All Files (42.28% covered at 0.69 hits/line)
259 files in total.
9757 relevant lines.
4125 lines covered and
5632 lines missed
-
1
require File.expand_path('../boot', __FILE__)
-
-
1
require 'rails/all'
-
-
1
if defined?(Bundler)
-
# If you precompile assets before deploying to production, use this line
-
1
Bundler.require *Rails.groups(:assets => %w(development test))
-
# If you want your assets lazily compiled in production, use this line
-
# Bundler.require(:default, :assets, Rails.env)
-
end
-
-
module ICA
-
class Application < Rails::Application
-
# Settings in config/environments/* take precedence over those specified here.
-
# Application configuration should go into files in config/initializers
-
# -- all .rb files in that directory are automatically loaded.
-
-
# Custom directories with classes and modules you want to be autoloadable.
-
# config.autoload_paths += %W(#{config.root}/extras)
-
-
# Only load the plugins named here, in the order given (default is alphabetical).
-
# :all can be used as a placeholder for all plugins not explicitly named.
-
# config.plugins = [ :exception_notification, :ssl_requirement, :all ]
-
-
# Activate observers that should always be running.
-
# config.active_record.observers = :cacher, :garbage_collector, :forum_observer
-
-
# Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
-
# Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
-
# config.time_zone = 'Central Time (US & Canada)'
-
-
# The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
-
# config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
-
# config.i18n.default_locale = :de
-
-
# Configure the default encoding used in templates for Ruby 1.9.
-
config.encoding = "utf-8"
-
-
# Configure sensitive parameters which will be filtered from the log file.
-
config.filter_parameters += [:password]
-
-
# Enable the asset pipeline
-
config.assets.enabled = true
-
-
# Version of your assets, change this if you want to expire all your assets
-
config.assets.version = '1.0'
-
-
# Remove field_with_error div wrapping invalid classes since it messes with Bootstrap styling
-
config.action_view.field_error_proc = Proc.new { |html_tag, instance| "#{html_tag}".html_safe }
-
end
-
end
-
1
require 'rubygems'
-
-
# Set up gems listed in the Gemfile.
-
1
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
-
-
1
require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])
-
# Load the rails application
-
1
require File.expand_path('../application', __FILE__)
-
-
# Initialize the rails application
-
ICA::Application.initialize!
-
#--
-
# Copyright (c) 2004-2011 David Heinemeier Hansson
-
#
-
# Permission is hereby granted, free of charge, to any person obtaining
-
# a copy of this software and associated documentation files (the
-
# "Software"), to deal in the Software without restriction, including
-
# without limitation the rights to use, copy, modify, merge, publish,
-
# distribute, sublicense, and/or sell copies of the Software, and to
-
# permit persons to whom the Software is furnished to do so, subject to
-
# the following conditions:
-
#
-
# The above copyright notice and this permission notice shall be
-
# included in all copies or substantial portions of the Software.
-
#
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
#++
-
-
1
actionpack_path = File.expand_path('../../../actionpack/lib', __FILE__)
-
1
$:.unshift(actionpack_path) if File.directory?(actionpack_path) && !$:.include?(actionpack_path)
-
-
1
require 'abstract_controller'
-
1
require 'action_view'
-
1
require 'action_mailer/version'
-
-
# Common Active Support usage in Action Mailer
-
1
require 'active_support/core_ext/class'
-
1
require 'active_support/core_ext/object/blank'
-
1
require 'active_support/core_ext/array/uniq_by'
-
1
require 'active_support/core_ext/module/attr_internal'
-
1
require 'active_support/core_ext/module/delegation'
-
1
require 'active_support/core_ext/string/inflections'
-
1
require 'active_support/lazy_load_hooks'
-
-
1
module ActionMailer
-
1
extend ::ActiveSupport::Autoload
-
-
1
autoload :AdvAttrAccessor
-
1
autoload :Collector
-
1
autoload :Base
-
1
autoload :DeliveryMethods
-
1
autoload :MailHelper
-
1
autoload :OldApi
-
1
autoload :TestCase
-
1
autoload :TestHelper
-
end
-
1
require "action_mailer"
-
1
require "rails"
-
1
require "abstract_controller/railties/routes_helpers"
-
-
1
module ActionMailer
-
1
class Railtie < Rails::Railtie
-
1
config.action_mailer = ActiveSupport::OrderedOptions.new
-
-
1
initializer "action_mailer.logger" do
-
ActiveSupport.on_load(:action_mailer) { self.logger ||= Rails.logger }
-
end
-
-
1
initializer "action_mailer.set_configs" do |app|
-
paths = app.config.paths
-
options = app.config.action_mailer
-
-
options.assets_dir ||= paths["public"].first
-
options.javascripts_dir ||= paths["public/javascripts"].first
-
options.stylesheets_dir ||= paths["public/stylesheets"].first
-
-
# make sure readers methods get compiled
-
options.asset_path ||= app.config.asset_path
-
options.asset_host ||= app.config.asset_host
-
-
ActiveSupport.on_load(:action_mailer) do
-
include AbstractController::UrlFor
-
extend ::AbstractController::Railties::RoutesHelpers.with(app.routes)
-
include app.routes.mounted_helpers
-
-
register_interceptors(options.delete(:interceptors))
-
register_observers(options.delete(:observers))
-
-
options.each { |k,v| send("#{k}=", v) }
-
end
-
end
-
-
1
initializer "action_mailer.compile_config_methods" do
-
ActiveSupport.on_load(:action_mailer) do
-
config.compile_methods! if config.respond_to?(:compile_methods!)
-
end
-
end
-
end
-
end
-
1
module ActionMailer
-
1
module VERSION #:nodoc:
-
1
MAJOR = 3
-
1
MINOR = 1
-
1
TINY = 0
-
1
PRE = nil
-
-
1
STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
-
end
-
end
-
1
activesupport_path = File.expand_path('../../../activesupport/lib', __FILE__)
-
1
$:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.include?(activesupport_path)
-
-
1
require 'action_pack'
-
1
require 'active_support/concern'
-
1
require 'active_support/ruby/shim'
-
1
require 'active_support/dependencies/autoload'
-
1
require 'active_support/core_ext/class/attribute'
-
1
require 'active_support/core_ext/module/attr_internal'
-
1
require 'active_support/core_ext/module/delegation'
-
1
require 'active_support/core_ext/module/anonymous'
-
1
require 'active_support/i18n'
-
-
1
module AbstractController
-
1
extend ActiveSupport::Autoload
-
-
1
autoload :Base
-
1
autoload :Callbacks
-
1
autoload :Collector
-
1
autoload :Helpers
-
1
autoload :Layouts
-
1
autoload :Logger
-
1
autoload :Rendering
-
1
autoload :Translation
-
1
autoload :AssetPaths
-
1
autoload :ViewPaths
-
1
autoload :UrlFor
-
end
-
1
module AbstractController
-
1
module Railties
-
1
module RoutesHelpers
-
1
def self.with(routes)
-
Module.new do
-
define_method(:inherited) do |klass|
-
super(klass)
-
if namespace = klass.parents.detect {|m| m.respond_to?(:_railtie) }
-
klass.send(:include, namespace._railtie.routes.url_helpers)
-
else
-
klass.send(:include, routes.url_helpers)
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
1
require 'abstract_controller'
-
1
require 'action_dispatch'
-
-
1
module ActionController
-
1
extend ActiveSupport::Autoload
-
-
1
autoload :Base
-
1
autoload :Caching
-
1
autoload :Metal
-
1
autoload :Middleware
-
-
1
autoload_under "metal" do
-
1
autoload :Compatibility
-
1
autoload :ConditionalGet
-
1
autoload :Cookies
-
1
autoload :DataStreaming
-
1
autoload :Flash
-
1
autoload :ForceSSL
-
1
autoload :Head
-
1
autoload :Helpers
-
1
autoload :HideActions
-
1
autoload :HttpAuthentication
-
1
autoload :ImplicitRender
-
1
autoload :Instrumentation
-
1
autoload :MimeResponds
-
1
autoload :ParamsWrapper
-
1
autoload :RackDelegation
-
1
autoload :Redirecting
-
1
autoload :Renderers
-
1
autoload :Rendering
-
1
autoload :RequestForgeryProtection
-
1
autoload :Rescue
-
1
autoload :Responder
-
1
autoload :SessionManagement
-
1
autoload :Streaming
-
1
autoload :Testing
-
1
autoload :UrlFor
-
end
-
-
1
autoload :Integration, 'action_controller/deprecated/integration_test'
-
1
autoload :IntegrationTest, 'action_controller/deprecated/integration_test'
-
1
autoload :PerformanceTest, 'action_controller/deprecated/performance_test'
-
1
autoload :UrlWriter, 'action_controller/deprecated'
-
1
autoload :Routing, 'action_controller/deprecated'
-
1
autoload :TestCase, 'action_controller/test_case'
-
1
autoload :TemplateAssertions, 'action_controller/test_case'
-
-
1
eager_autoload do
-
1
autoload :RecordIdentifier
-
-
# TODO: Don't autoload exceptions, setup explicit
-
# requires for files that need them
-
1
autoload_at "action_controller/metal/exceptions" do
-
1
autoload :ActionControllerError
-
1
autoload :RenderError
-
1
autoload :RoutingError
-
1
autoload :MethodNotAllowed
-
1
autoload :NotImplemented
-
1
autoload :UnknownController
-
1
autoload :MissingFile
-
1
autoload :RenderError
-
1
autoload :SessionOverflowError
-
1
autoload :UnknownHttpMethod
-
end
-
end
-
end
-
-
# All of these simply register additional autoloads
-
1
require 'action_view'
-
1
require 'action_controller/vendor/html-scanner'
-
-
# Common Active Support usage in Action Controller
-
1
require 'active_support/concern'
-
1
require 'active_support/core_ext/class/attribute_accessors'
-
1
require 'active_support/core_ext/load_error'
-
1
require 'active_support/core_ext/module/attr_internal'
-
1
require 'active_support/core_ext/module/delegation'
-
1
require 'active_support/core_ext/name_error'
-
1
require 'active_support/core_ext/uri'
-
1
require 'active_support/inflector'
-
1
require "rails"
-
1
require "action_controller"
-
1
require "action_dispatch/railtie"
-
1
require "action_view/railtie"
-
1
require "abstract_controller/railties/routes_helpers"
-
1
require "action_controller/railties/paths"
-
-
1
module ActionController
-
1
class Railtie < Rails::Railtie
-
1
config.action_controller = ActiveSupport::OrderedOptions.new
-
-
1
initializer "action_controller.logger" do
-
ActiveSupport.on_load(:action_controller) { self.logger ||= Rails.logger }
-
end
-
-
1
initializer "action_controller.initialize_framework_caches" do
-
ActiveSupport.on_load(:action_controller) { self.cache_store ||= RAILS_CACHE }
-
end
-
-
1
initializer "action_controller.set_configs" do |app|
-
paths = app.config.paths
-
options = app.config.action_controller
-
-
options.assets_dir ||= paths["public"].first
-
options.javascripts_dir ||= paths["public/javascripts"].first
-
options.stylesheets_dir ||= paths["public/stylesheets"].first
-
options.page_cache_directory ||= paths["public"].first
-
-
# make sure readers methods get compiled
-
options.asset_path ||= app.config.asset_path
-
options.asset_host ||= app.config.asset_host
-
-
ActiveSupport.on_load(:action_controller) do
-
include app.routes.mounted_helpers
-
extend ::AbstractController::Railties::RoutesHelpers.with(app.routes)
-
extend ::ActionController::Railties::Paths.with(app)
-
options.each { |k,v| send("#{k}=", v) }
-
end
-
end
-
-
1
initializer "action_controller.compile_config_methods" do
-
ActiveSupport.on_load(:action_controller) do
-
config.compile_methods! if config.respond_to?(:compile_methods!)
-
end
-
end
-
end
-
end
-
1
module ActionController
-
1
module Railties
-
1
module Paths
-
1
def self.with(app)
-
Module.new do
-
define_method(:inherited) do |klass|
-
super(klass)
-
-
if namespace = klass.parents.detect {|m| m.respond_to?(:_railtie) }
-
paths = namespace._railtie.paths["app/helpers"].existent
-
else
-
paths = app.config.helpers_paths
-
end
-
-
klass.helpers_path = paths
-
if klass.superclass == ActionController::Base && ActionController::Base.include_all_helpers
-
klass.helper :all
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
1
$LOAD_PATH << "#{File.dirname(__FILE__)}/html-scanner"
-
-
1
module HTML
-
1
extend ActiveSupport::Autoload
-
-
1
eager_autoload do
-
1
autoload :CDATA, 'html/node'
-
1
autoload :Document, 'html/document'
-
1
autoload :FullSanitizer, 'html/sanitizer'
-
1
autoload :LinkSanitizer, 'html/sanitizer'
-
1
autoload :Node, 'html/node'
-
1
autoload :Sanitizer, 'html/sanitizer'
-
1
autoload :Selector, 'html/selector'
-
1
autoload :Tag, 'html/node'
-
1
autoload :Text, 'html/node'
-
1
autoload :Tokenizer, 'html/tokenizer'
-
1
autoload :Version, 'html/version'
-
1
autoload :WhiteListSanitizer, 'html/sanitizer'
-
end
-
end
-
#--
-
# Copyright (c) 2004-2011 David Heinemeier Hansson
-
#
-
# Permission is hereby granted, free of charge, to any person obtaining
-
# a copy of this software and associated documentation files (the
-
# "Software"), to deal in the Software without restriction, including
-
# without limitation the rights to use, copy, modify, merge, publish,
-
# distribute, sublicense, and/or sell copies of the Software, and to
-
# permit persons to whom the Software is furnished to do so, subject to
-
# the following conditions:
-
#
-
# The above copyright notice and this permission notice shall be
-
# included in all copies or substantial portions of the Software.
-
#
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
#++
-
-
1
require 'active_support/ruby/shim'
-
1
require 'active_support/core_ext/class/attribute_accessors'
-
-
1
require 'action_pack'
-
-
1
module ActionView
-
1
extend ActiveSupport::Autoload
-
-
1
eager_autoload do
-
1
autoload :AssetPaths
-
1
autoload :Base
-
1
autoload :Context
-
1
autoload :Helpers
-
1
autoload :LookupContext
-
1
autoload :PathSet
-
1
autoload :Template
-
1
autoload :TestCase
-
-
1
autoload_under "renderer" do
-
1
autoload :Renderer
-
1
autoload :AbstractRenderer
-
1
autoload :PartialRenderer
-
1
autoload :TemplateRenderer
-
1
autoload :StreamingTemplateRenderer
-
end
-
-
1
autoload_at "action_view/template/resolver" do
-
1
autoload :Resolver
-
1
autoload :PathResolver
-
1
autoload :FileSystemResolver
-
1
autoload :OptimizedFileSystemResolver
-
1
autoload :FallbackFileSystemResolver
-
end
-
-
1
autoload_at "action_view/buffers" do
-
1
autoload :OutputBuffer
-
1
autoload :StreamingBuffer
-
end
-
-
1
autoload_at "action_view/flows" do
-
1
autoload :OutputFlow
-
1
autoload :StreamingFlow
-
end
-
-
1
autoload_at "action_view/template/error" do
-
1
autoload :MissingTemplate
-
1
autoload :ActionViewError
-
1
autoload :EncodingError
-
1
autoload :TemplateError
-
1
autoload :WrongEncodingError
-
end
-
-
1
autoload_at "action_view/template" do
-
1
autoload :TemplateHandler
-
1
autoload :TemplateHandlers
-
end
-
end
-
-
1
ENCODING_FLAG = '#.*coding[:=]\s*(\S+)[ \t]*'
-
end
-
-
1
require 'active_support/i18n'
-
1
require 'active_support/core_ext/string/output_safety'
-
-
1
I18n.load_path << "#{File.dirname(__FILE__)}/action_view/locale/en.yml"
-
1
require "action_view"
-
1
require "rails"
-
-
1
module ActionView
-
# = Action View Railtie
-
1
class Railtie < Rails::Railtie
-
1
config.action_view = ActiveSupport::OrderedOptions.new
-
1
config.action_view.stylesheet_expansions = {}
-
1
config.action_view.javascript_expansions = { :defaults => %w(jquery jquery_ujs) }
-
-
1
initializer "action_view.cache_asset_ids" do |app|
-
unless app.config.cache_classes
-
ActiveSupport.on_load(:action_view) do
-
ActionView::Helpers::AssetTagHelper::AssetPaths.cache_asset_ids = false
-
end
-
end
-
end
-
-
1
initializer "action_view.javascript_expansions" do |app|
-
ActiveSupport.on_load(:action_view) do
-
ActionView::Helpers::AssetTagHelper.register_javascript_expansion(
-
app.config.action_view.delete(:javascript_expansions)
-
)
-
-
ActionView::Helpers::AssetTagHelper.register_stylesheet_expansion(
-
app.config.action_view.delete(:stylesheet_expansions)
-
)
-
end
-
end
-
-
1
initializer "action_view.set_configs" do |app|
-
ActiveSupport.on_load(:action_view) do
-
app.config.action_view.each do |k,v|
-
send "#{k}=", v
-
end
-
end
-
end
-
-
1
initializer "action_view.caching" do |app|
-
ActiveSupport.on_load(:action_view) do
-
if app.config.action_view.cache_template_loading.nil?
-
ActionView::Resolver.caching = app.config.cache_classes
-
end
-
end
-
end
-
end
-
end
-
1
module Sprockets
-
1
autoload :Helpers, "sprockets/helpers"
-
1
autoload :LazyCompressor, "sprockets/compressors"
-
1
autoload :NullCompressor, "sprockets/compressors"
-
-
# TODO: Get rid of config.assets.enabled
-
1
class Railtie < ::Rails::Railtie
-
1
config.default_asset_host_protocol = :relative
-
-
1
rake_tasks do
-
load "sprockets/assets.rake"
-
end
-
-
1
initializer "sprockets.environment" do |app|
-
config = app.config
-
next unless config.assets.enabled
-
-
require 'sprockets'
-
-
app.assets = Sprockets::Environment.new(app.root.to_s) do |env|
-
env.logger = ::Rails.logger
-
env.version = ::Rails.env + "-#{config.assets.version}"
-
-
if config.assets.cache_store != false
-
env.cache = ActiveSupport::Cache.lookup_store(config.assets.cache_store) || ::Rails.cache
-
end
-
end
-
-
if config.assets.manifest
-
path = File.join(config.assets.manifest, "manifest.yml")
-
else
-
path = File.join(Rails.public_path, config.assets.prefix, "manifest.yml")
-
end
-
-
if File.exist?(path)
-
config.assets.digests = YAML.load_file(path)
-
end
-
-
ActiveSupport.on_load(:action_view) do
-
include ::Sprockets::Helpers::RailsHelper
-
-
app.assets.context_class.instance_eval do
-
include ::Sprockets::Helpers::RailsHelper
-
end
-
end
-
end
-
-
# We need to configure this after initialization to ensure we collect
-
# paths from all engines. This hook is invoked exactly before routes
-
# are compiled, and so that other Railties have an opportunity to
-
# register compressors.
-
1
config.after_initialize do |app|
-
next unless app.assets
-
config = app.config
-
-
config.assets.paths.each { |path| app.assets.append_path(path) }
-
-
if config.assets.compress
-
# temporarily hardcode default JS compressor to uglify. Soon, it will work
-
# the same as SCSS, where a default plugin sets the default.
-
unless config.assets.js_compressor == false
-
app.assets.js_compressor = LazyCompressor.new { expand_js_compressor(config.assets.js_compressor || :uglifier) }
-
end
-
-
unless config.assets.css_compressor == false
-
app.assets.css_compressor = LazyCompressor.new { expand_css_compressor(config.assets.css_compressor) }
-
end
-
end
-
-
app.routes.prepend do
-
mount app.assets => config.assets.prefix
-
end
-
-
if config.action_controller.perform_caching
-
app.assets = app.assets.index
-
end
-
end
-
-
1
protected
-
1
def expand_js_compressor(sym)
-
case sym
-
when :closure
-
require 'closure-compiler'
-
Closure::Compiler.new
-
when :uglifier
-
require 'uglifier'
-
Uglifier.new
-
when :yui
-
require 'yui/compressor'
-
YUI::JavaScriptCompressor.new
-
else
-
sym
-
end
-
end
-
-
1
def expand_css_compressor(sym)
-
case sym
-
when :yui
-
require 'yui/compressor'
-
YUI::CssCompressor.new
-
else
-
sym
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/hash/keys'
-
1
require 'active_support/core_ext/class/attribute'
-
-
1
module ActiveModel
-
1
class MissingAttributeError < NoMethodError
-
end
-
# == Active Model Attribute Methods
-
#
-
# <tt>ActiveModel::AttributeMethods</tt> provides a way to add prefixes and suffixes
-
# to your methods as well as handling the creation of Active Record like class methods
-
# such as +table_name+.
-
#
-
# The requirements to implement ActiveModel::AttributeMethods are to:
-
#
-
# * <tt>include ActiveModel::AttributeMethods</tt> in your object
-
# * Call each Attribute Method module method you want to add, such as
-
# attribute_method_suffix or attribute_method_prefix
-
# * Call <tt>define_attribute_methods</tt> after the other methods are
-
# called.
-
# * Define the various generic +_attribute+ methods that you have declared
-
#
-
# A minimal implementation could be:
-
#
-
# class Person
-
# include ActiveModel::AttributeMethods
-
#
-
# attribute_method_affix :prefix => 'reset_', :suffix => '_to_default!'
-
# attribute_method_suffix '_contrived?'
-
# attribute_method_prefix 'clear_'
-
# define_attribute_methods ['name']
-
#
-
# attr_accessor :name
-
#
-
# private
-
#
-
# def attribute_contrived?(attr)
-
# true
-
# end
-
#
-
# def clear_attribute(attr)
-
# send("#{attr}=", nil)
-
# end
-
#
-
# def reset_attribute_to_default!(attr)
-
# send("#{attr}=", "Default Name")
-
# end
-
# end
-
#
-
# Note that whenever you include ActiveModel::AttributeMethods in your class,
-
# it requires you to implement an <tt>attributes</tt> method which returns a hash
-
# with each attribute name in your model as hash key and the attribute value as
-
# hash value.
-
#
-
# Hash keys must be strings.
-
#
-
1
module AttributeMethods
-
1
extend ActiveSupport::Concern
-
-
1
COMPILABLE_REGEXP = /\A[a-zA-Z_]\w*[!?=]?\z/
-
-
1
included do
-
class_attribute :attribute_method_matchers, :instance_writer => false
-
self.attribute_method_matchers = []
-
end
-
-
1
module ClassMethods
-
# Defines an "attribute" method (like +inheritance_column+ or +table_name+).
-
# A new (class) method will be created with the given name. If a value is
-
# specified, the new method will return that value (as a string).
-
# Otherwise, the given block will be used to compute the value of the
-
# method.
-
#
-
# The original method will be aliased, with the new name being prefixed
-
# with "original_". This allows the new method to access the original
-
# value.
-
#
-
# Example:
-
#
-
# class Person
-
#
-
# include ActiveModel::AttributeMethods
-
#
-
# cattr_accessor :primary_key
-
# cattr_accessor :inheritance_column
-
#
-
# define_attr_method :primary_key, "sysid"
-
# define_attr_method( :inheritance_column ) do
-
# original_inheritance_column + "_id"
-
# end
-
#
-
# end
-
#
-
# Provides you with:
-
#
-
# AttributePerson.primary_key
-
# # => "sysid"
-
# AttributePerson.inheritance_column = 'address'
-
# AttributePerson.inheritance_column
-
# # => 'address_id'
-
1
def define_attr_method(name, value=nil, &block)
-
sing = singleton_class
-
sing.class_eval <<-eorb, __FILE__, __LINE__ + 1
-
if method_defined?('original_#{name}')
-
undef :'original_#{name}'
-
end
-
alias_method :'original_#{name}', :'#{name}'
-
eorb
-
if block_given?
-
sing.send :define_method, name, &block
-
else
-
# If we can compile the method name, do it. Otherwise use define_method.
-
# This is an important *optimization*, please don't change it. define_method
-
# has slower dispatch and consumes more memory.
-
if name =~ COMPILABLE_REGEXP
-
sing.class_eval <<-RUBY, __FILE__, __LINE__ + 1
-
def #{name}; #{value.nil? ? 'nil' : value.to_s.inspect}; end
-
RUBY
-
else
-
value = value.to_s if value
-
sing.send(:define_method, name) { value }
-
end
-
end
-
end
-
-
# Declares a method available for all attributes with the given prefix.
-
# Uses +method_missing+ and <tt>respond_to?</tt> to rewrite the method.
-
#
-
# #{prefix}#{attr}(*args, &block)
-
#
-
# to
-
#
-
# #{prefix}attribute(#{attr}, *args, &block)
-
#
-
# An instance method <tt>#{prefix}attribute</tt> must exist and accept
-
# at least the +attr+ argument.
-
#
-
# For example:
-
#
-
# class Person
-
#
-
# include ActiveModel::AttributeMethods
-
# attr_accessor :name
-
# attribute_method_prefix 'clear_'
-
# define_attribute_methods [:name]
-
#
-
# private
-
#
-
# def clear_attribute(attr)
-
# send("#{attr}=", nil)
-
# end
-
# end
-
#
-
# person = Person.new
-
# person.name = "Bob"
-
# person.name # => "Bob"
-
# person.clear_name
-
# person.name # => nil
-
1
def attribute_method_prefix(*prefixes)
-
self.attribute_method_matchers += prefixes.map { |prefix| AttributeMethodMatcher.new :prefix => prefix }
-
undefine_attribute_methods
-
end
-
-
# Declares a method available for all attributes with the given suffix.
-
# Uses +method_missing+ and <tt>respond_to?</tt> to rewrite the method.
-
#
-
# #{attr}#{suffix}(*args, &block)
-
#
-
# to
-
#
-
# attribute#{suffix}(#{attr}, *args, &block)
-
#
-
# An <tt>attribute#{suffix}</tt> instance method must exist and accept at least
-
# the +attr+ argument.
-
#
-
# For example:
-
#
-
# class Person
-
#
-
# include ActiveModel::AttributeMethods
-
# attr_accessor :name
-
# attribute_method_suffix '_short?'
-
# define_attribute_methods [:name]
-
#
-
# private
-
#
-
# def attribute_short?(attr)
-
# send(attr).length < 5
-
# end
-
# end
-
#
-
# person = Person.new
-
# person.name = "Bob"
-
# person.name # => "Bob"
-
# person.name_short? # => true
-
1
def attribute_method_suffix(*suffixes)
-
self.attribute_method_matchers += suffixes.map { |suffix| AttributeMethodMatcher.new :suffix => suffix }
-
undefine_attribute_methods
-
end
-
-
# Declares a method available for all attributes with the given prefix
-
# and suffix. Uses +method_missing+ and <tt>respond_to?</tt> to rewrite
-
# the method.
-
#
-
# #{prefix}#{attr}#{suffix}(*args, &block)
-
#
-
# to
-
#
-
# #{prefix}attribute#{suffix}(#{attr}, *args, &block)
-
#
-
# An <tt>#{prefix}attribute#{suffix}</tt> instance method must exist and
-
# accept at least the +attr+ argument.
-
#
-
# For example:
-
#
-
# class Person
-
#
-
# include ActiveModel::AttributeMethods
-
# attr_accessor :name
-
# attribute_method_affix :prefix => 'reset_', :suffix => '_to_default!'
-
# define_attribute_methods [:name]
-
#
-
# private
-
#
-
# def reset_attribute_to_default!(attr)
-
# ...
-
# end
-
# end
-
#
-
# person = Person.new
-
# person.name # => 'Gem'
-
# person.reset_name_to_default!
-
# person.name # => 'Gemma'
-
1
def attribute_method_affix(*affixes)
-
self.attribute_method_matchers += affixes.map { |affix| AttributeMethodMatcher.new :prefix => affix[:prefix], :suffix => affix[:suffix] }
-
undefine_attribute_methods
-
end
-
-
1
def alias_attribute(new_name, old_name)
-
attribute_method_matchers.each do |matcher|
-
matcher_new = matcher.method_name(new_name).to_s
-
matcher_old = matcher.method_name(old_name).to_s
-
-
if matcher_new =~ COMPILABLE_REGEXP && matcher_old =~ COMPILABLE_REGEXP
-
module_eval <<-RUBY, __FILE__, __LINE__ + 1
-
def #{matcher_new}(*args)
-
send(:#{matcher_old}, *args)
-
end
-
RUBY
-
else
-
define_method(matcher_new) do |*args|
-
send(matcher_old, *args)
-
end
-
end
-
end
-
end
-
-
# Declares the attributes that should be prefixed and suffixed by
-
# ActiveModel::AttributeMethods.
-
#
-
# To use, pass in an array of attribute names (as strings or symbols),
-
# be sure to declare +define_attribute_methods+ after you define any
-
# prefix, suffix or affix methods, or they will not hook in.
-
#
-
# class Person
-
#
-
# include ActiveModel::AttributeMethods
-
# attr_accessor :name, :age, :address
-
# attribute_method_prefix 'clear_'
-
#
-
# # Call to define_attribute_methods must appear after the
-
# # attribute_method_prefix, attribute_method_suffix or
-
# # attribute_method_affix declares.
-
# define_attribute_methods [:name, :age, :address]
-
#
-
# private
-
#
-
# def clear_attribute(attr)
-
# ...
-
# end
-
# end
-
1
def define_attribute_methods(attr_names)
-
attr_names.each { |attr_name| define_attribute_method(attr_name) }
-
end
-
-
1
def define_attribute_method(attr_name)
-
attribute_method_matchers.each do |matcher|
-
unless instance_method_already_implemented?(matcher.method_name(attr_name))
-
generate_method = "define_method_#{matcher.prefix}attribute#{matcher.suffix}"
-
-
if respond_to?(generate_method)
-
send(generate_method, attr_name)
-
else
-
method_name = matcher.method_name(attr_name)
-
-
generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
-
if method_defined?('#{method_name}')
-
undef :'#{method_name}'
-
end
-
RUBY
-
-
if method_name.to_s =~ COMPILABLE_REGEXP
-
generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
-
def #{method_name}(*args)
-
send(:#{matcher.method_missing_target}, '#{attr_name}', *args)
-
end
-
RUBY
-
else
-
generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
-
define_method('#{method_name}') do |*args|
-
send('#{matcher.method_missing_target}', '#{attr_name}', *args)
-
end
-
RUBY
-
end
-
end
-
end
-
end
-
end
-
-
# Removes all the previously dynamically defined methods from the class
-
1
def undefine_attribute_methods
-
generated_attribute_methods.module_eval do
-
instance_methods.each { |m| undef_method(m) }
-
end
-
end
-
-
# Returns true if the attribute methods defined have been generated.
-
1
def generated_attribute_methods #:nodoc:
-
@generated_attribute_methods ||= begin
-
mod = Module.new
-
include mod
-
mod
-
end
-
end
-
-
1
protected
-
1
def instance_method_already_implemented?(method_name)
-
method_defined?(method_name)
-
end
-
-
1
private
-
1
class AttributeMethodMatcher
-
1
attr_reader :prefix, :suffix, :method_missing_target
-
-
1
AttributeMethodMatch = Struct.new(:target, :attr_name)
-
-
1
def initialize(options = {})
-
options.symbolize_keys!
-
@prefix, @suffix = options[:prefix] || '', options[:suffix] || ''
-
@regex = /^(#{Regexp.escape(@prefix)})(.+?)(#{Regexp.escape(@suffix)})$/
-
@method_missing_target = "#{@prefix}attribute#{@suffix}"
-
@method_name = "#{prefix}%s#{suffix}"
-
end
-
-
1
def match(method_name)
-
if @regex =~ method_name
-
AttributeMethodMatch.new(method_missing_target, $2)
-
else
-
nil
-
end
-
end
-
-
1
def method_name(attr_name)
-
@method_name % attr_name
-
end
-
end
-
end
-
-
# Allows access to the object attributes, which are held in the
-
# <tt>@attributes</tt> hash, as though they were first-class methods. So a
-
# Person class with a name attribute can use Person#name and Person#name=
-
# and never directly use the attributes hash -- except for multiple assigns
-
# with ActiveRecord#attributes=. A Milestone class can also ask
-
# Milestone#completed? to test that the completed attribute is not +nil+
-
# or 0.
-
#
-
# It's also possible to instantiate related objects, so a Client class
-
# belonging to the clients table with a +master_id+ foreign key can
-
# instantiate master through Client#master.
-
1
def method_missing(method_id, *args, &block)
-
method_name = method_id.to_s
-
if match = match_attribute_method?(method_name)
-
guard_private_attribute_method!(method_name, args)
-
return __send__(match.target, match.attr_name, *args, &block)
-
end
-
super
-
end
-
-
# A Person object with a name attribute can ask <tt>person.respond_to?(:name)</tt>,
-
# <tt>person.respond_to?(:name=)</tt>, and <tt>person.respond_to?(:name?)</tt>
-
# which will all return +true+.
-
1
alias :respond_to_without_attributes? :respond_to?
-
1
def respond_to?(method, include_private_methods = false)
-
if super
-
return true
-
elsif !include_private_methods && super(method, true)
-
# If we're here then we haven't found among non-private methods
-
# but found among all methods. Which means that the given method is private.
-
return false
-
elsif match_attribute_method?(method.to_s)
-
return true
-
end
-
super
-
end
-
-
1
protected
-
1
def attribute_method?(attr_name)
-
attributes.include?(attr_name)
-
end
-
-
1
private
-
# Returns a struct representing the matching attribute method.
-
# The struct's attributes are prefix, base and suffix.
-
1
def match_attribute_method?(method_name)
-
self.class.attribute_method_matchers.each do |method|
-
if (match = method.match(method_name)) && attribute_method?(match.attr_name)
-
return match
-
end
-
end
-
nil
-
end
-
-
# prevent method_missing from calling private methods with #send
-
1
def guard_private_attribute_method!(method_name, args)
-
if self.class.private_method_defined?(method_name)
-
raise NoMethodError.new("Attempt to call private method `#{method_name}'", method_name, args)
-
end
-
end
-
-
1
def missing_attribute(attr_name, stack)
-
raise ActiveModel::MissingAttributeError, "missing attribute: #{attr_name}", stack
-
end
-
end
-
end
-
1
require "active_model"
-
1
require "rails"
-
#--
-
# Copyright (c) 2004-2011 David Heinemeier Hansson
-
#
-
# Permission is hereby granted, free of charge, to any person obtaining
-
# a copy of this software and associated documentation files (the
-
# "Software"), to deal in the Software without restriction, including
-
# without limitation the rights to use, copy, modify, merge, publish,
-
# distribute, sublicense, and/or sell copies of the Software, and to
-
# permit persons to whom the Software is furnished to do so, subject to
-
# the following conditions:
-
#
-
# The above copyright notice and this permission notice shall be
-
# included in all copies or substantial portions of the Software.
-
#
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
#++
-
-
-
1
activesupport_path = File.expand_path('../../../activesupport/lib', __FILE__)
-
1
$:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.include?(activesupport_path)
-
-
1
activemodel_path = File.expand_path('../../../activemodel/lib', __FILE__)
-
1
$:.unshift(activemodel_path) if File.directory?(activemodel_path) && !$:.include?(activemodel_path)
-
-
1
require 'active_support'
-
1
require 'active_support/i18n'
-
1
require 'active_model'
-
1
require 'arel'
-
-
1
require 'active_record/version'
-
-
1
module ActiveRecord
-
1
extend ActiveSupport::Autoload
-
-
1
eager_autoload do
-
1
autoload :ActiveRecordError, 'active_record/errors'
-
1
autoload :ConnectionNotEstablished, 'active_record/errors'
-
-
1
autoload :Aggregations
-
1
autoload :Associations
-
1
autoload :AttributeMethods
-
1
autoload :AutosaveAssociation
-
-
1
autoload :Relation
-
-
1
autoload_under 'relation' do
-
1
autoload :QueryMethods
-
1
autoload :FinderMethods
-
1
autoload :Calculations
-
1
autoload :PredicateBuilder
-
1
autoload :SpawnMethods
-
1
autoload :Batches
-
end
-
-
1
autoload :Base
-
1
autoload :Callbacks
-
1
autoload :CounterCache
-
1
autoload :DynamicFinderMatch
-
1
autoload :DynamicScopeMatch
-
1
autoload :Migration
-
1
autoload :Migrator, 'active_record/migration'
-
1
autoload :NamedScope
-
1
autoload :NestedAttributes
-
1
autoload :Observer
-
1
autoload :Persistence
-
1
autoload :QueryCache
-
1
autoload :Reflection
-
1
autoload :Schema
-
1
autoload :SchemaDumper
-
1
autoload :Serialization
-
1
autoload :SessionStore
-
1
autoload :Timestamp
-
1
autoload :Transactions
-
1
autoload :Validations
-
1
autoload :IdentityMap
-
end
-
-
1
module Coders
-
1
autoload :YAMLColumn, 'active_record/coders/yaml_column'
-
end
-
-
1
module AttributeMethods
-
1
extend ActiveSupport::Autoload
-
-
1
eager_autoload do
-
1
autoload :BeforeTypeCast
-
1
autoload :Dirty
-
1
autoload :PrimaryKey
-
1
autoload :Query
-
1
autoload :Read
-
1
autoload :TimeZoneConversion
-
1
autoload :Write
-
end
-
end
-
-
1
module Locking
-
1
extend ActiveSupport::Autoload
-
-
1
eager_autoload do
-
1
autoload :Optimistic
-
1
autoload :Pessimistic
-
end
-
end
-
-
1
module ConnectionAdapters
-
1
extend ActiveSupport::Autoload
-
-
1
eager_autoload do
-
1
autoload :AbstractAdapter
-
1
autoload :ConnectionManagement, "active_record/connection_adapters/abstract/connection_pool"
-
end
-
end
-
-
1
autoload :TestCase
-
1
autoload :TestFixtures, 'active_record/fixtures'
-
end
-
-
1
ActiveSupport.on_load(:active_record) do
-
Arel::Table.engine = self
-
end
-
-
1
I18n.load_path << File.dirname(__FILE__) + '/active_record/locale/en.yml'
-
1
require 'active_support/core_ext/enumerable'
-
-
1
module ActiveRecord
-
# = Active Record Attribute Methods
-
1
module AttributeMethods #:nodoc:
-
1
extend ActiveSupport::Concern
-
1
include ActiveModel::AttributeMethods
-
-
1
module ClassMethods
-
# Generates all the attribute related methods for columns in the database
-
# accessors, mutators and query methods.
-
1
def define_attribute_methods
-
return if attribute_methods_generated?
-
super(column_names)
-
@attribute_methods_generated = true
-
end
-
-
1
def attribute_methods_generated?
-
@attribute_methods_generated ||= false
-
end
-
-
1
def undefine_attribute_methods(*args)
-
super
-
@attribute_methods_generated = false
-
end
-
-
# Checks whether the method is defined in the model or any of its subclasses
-
# that also derive from Active Record. Raises DangerousAttributeError if the
-
# method is defined by Active Record though.
-
1
def instance_method_already_implemented?(method_name)
-
method_name = method_name.to_s
-
index = ancestors.index(ActiveRecord::Base) || ancestors.length
-
@_defined_class_methods ||= ancestors.first(index).map { |m|
-
m.instance_methods(false) | m.private_instance_methods(false)
-
}.flatten.map {|m| m.to_s }.to_set
-
-
@@_defined_activerecord_methods ||= defined_activerecord_methods
-
raise DangerousAttributeError, "#{method_name} is defined by ActiveRecord" if @@_defined_activerecord_methods.include?(method_name)
-
@_defined_class_methods.include?(method_name)
-
end
-
-
1
def defined_activerecord_methods
-
active_record = ActiveRecord::Base
-
super_klass = ActiveRecord::Base.superclass
-
methods = (active_record.instance_methods - super_klass.instance_methods) +
-
(active_record.private_instance_methods - super_klass.private_instance_methods)
-
methods.map {|m| m.to_s }.to_set
-
end
-
end
-
-
1
def method_missing(method_id, *args, &block)
-
# If we haven't generated any methods yet, generate them, then
-
# see if we've created the method we're looking for.
-
if !self.class.attribute_methods_generated?
-
self.class.define_attribute_methods
-
method_name = method_id.to_s
-
guard_private_attribute_method!(method_name, args)
-
send(method_id, *args, &block)
-
else
-
super
-
end
-
end
-
-
1
def respond_to?(name, include_private = false)
-
self.class.define_attribute_methods unless self.class.attribute_methods_generated?
-
super
-
end
-
-
1
protected
-
1
def attribute_method?(attr_name)
-
attr_name == 'id' || (defined?(@attributes) && @attributes.include?(attr_name))
-
end
-
end
-
end
-
1
require "active_record"
-
1
require "rails"
-
1
require "active_model/railtie"
-
-
# For now, action_controller must always be present with
-
# rails, so let's make sure that it gets required before
-
# here. This is needed for correctly setting up the middleware.
-
# In the future, this might become an optional require.
-
1
require "action_controller/railtie"
-
-
1
module ActiveRecord
-
# = Active Record Railtie
-
1
class Railtie < Rails::Railtie
-
1
config.active_record = ActiveSupport::OrderedOptions.new
-
-
1
config.app_generators.orm :active_record, :migration => true,
-
:timestamps => true
-
-
1
config.app_middleware.insert_after "::ActionDispatch::Callbacks",
-
"ActiveRecord::QueryCache"
-
-
1
config.app_middleware.insert_after "::ActionDispatch::Callbacks",
-
"ActiveRecord::ConnectionAdapters::ConnectionManagement"
-
-
1
rake_tasks do
-
load "active_record/railties/databases.rake"
-
end
-
-
# When loading console, force ActiveRecord::Base to be loaded
-
# to avoid cross references when loading a constant for the
-
# first time. Also, make it output to STDERR.
-
1
console do |app|
-
require "active_record/railties/console_sandbox" if app.sandbox?
-
ActiveRecord::Base.logger = Logger.new(STDERR)
-
end
-
-
1
initializer "active_record.initialize_timezone" do
-
ActiveSupport.on_load(:active_record) do
-
self.time_zone_aware_attributes = true
-
self.default_timezone = :utc
-
end
-
end
-
-
1
initializer "active_record.logger" do
-
ActiveSupport.on_load(:active_record) { self.logger ||= ::Rails.logger }
-
end
-
-
1
initializer "active_record.identity_map" do |app|
-
config.app_middleware.insert_after "::ActionDispatch::Callbacks",
-
"ActiveRecord::IdentityMap::Middleware" if config.active_record.delete(:identity_map)
-
end
-
-
1
initializer "active_record.set_configs" do |app|
-
ActiveSupport.on_load(:active_record) do
-
if app.config.active_record.delete(:whitelist_attributes)
-
attr_accessible(nil)
-
end
-
app.config.active_record.each do |k,v|
-
send "#{k}=", v
-
end
-
end
-
end
-
-
# This sets the database configuration from Configuration#database_configuration
-
# and then establishes the connection.
-
1
initializer "active_record.initialize_database" do |app|
-
ActiveSupport.on_load(:active_record) do
-
self.configurations = app.config.database_configuration
-
establish_connection
-
end
-
end
-
-
# Expose database runtime to controller for logging.
-
1
initializer "active_record.log_runtime" do |app|
-
require "active_record/railties/controller_runtime"
-
ActiveSupport.on_load(:action_controller) do
-
include ActiveRecord::Railties::ControllerRuntime
-
end
-
end
-
-
1
initializer "active_record.set_dispatch_hooks", :before => :set_clear_dependencies_hook do |app|
-
ActiveSupport.on_load(:active_record) do
-
ActionDispatch::Reloader.to_cleanup do
-
ActiveRecord::Base.clear_reloadable_connections!
-
ActiveRecord::Base.clear_cache!
-
end
-
end
-
end
-
-
1
config.after_initialize do
-
ActiveSupport.on_load(:active_record) do
-
instantiate_observers
-
-
ActionDispatch::Reloader.to_prepare do
-
ActiveRecord::Base.instantiate_observers
-
end
-
end
-
end
-
-
1
config.after_initialize do
-
container = :"activerecord.attributes"
-
lookup = I18n.t(container, :default => {})
-
if lookup.is_a?(Hash)
-
lookup.each do |key, value|
-
if value.is_a?(Hash) && value.any? { |k,v| v.is_a?(Hash) }
-
$stderr.puts "[DEPRECATION WARNING] Nested I18n namespace lookup under \"#{container}.#{key}\" is no longer supported"
-
end
-
end
-
end
-
-
container = :"activerecord.models"
-
lookup = I18n.t(container, :default => {})
-
if lookup.is_a?(Hash)
-
lookup.each do |key, value|
-
if value.is_a?(Hash) && !value.key?(:one)
-
$stderr.puts "[DEPRECATION WARNING] Nested I18n namespace lookup under \"#{container}.#{key}\" is no longer supported"
-
end
-
end
-
end
-
end
-
-
end
-
end
-
1
module ActiveRecord
-
1
module VERSION #:nodoc:
-
1
MAJOR = 3
-
1
MINOR = 1
-
1
TINY = 0
-
1
PRE = nil
-
-
1
STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
-
end
-
end
-
#--
-
# Copyright (c) 2006-2011 David Heinemeier Hansson
-
#
-
# Permission is hereby granted, free of charge, to any person obtaining
-
# a copy of this software and associated documentation files (the
-
# "Software"), to deal in the Software without restriction, including
-
# without limitation the rights to use, copy, modify, merge, publish,
-
# distribute, sublicense, and/or sell copies of the Software, and to
-
# permit persons to whom the Software is furnished to do so, subject to
-
# the following conditions:
-
#
-
# The above copyright notice and this permission notice shall be
-
# included in all copies or substantial portions of the Software.
-
#
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
#++
-
-
1
activesupport_path = File.expand_path('../../../activesupport/lib', __FILE__)
-
1
$:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.include?(activesupport_path)
-
-
1
activemodel_path = File.expand_path('../../../activemodel/lib', __FILE__)
-
1
$:.unshift(activemodel_path) if File.directory?(activemodel_path) && !$:.include?(activemodel_path)
-
-
1
require 'active_support'
-
1
require 'active_model'
-
1
require 'active_resource/version'
-
-
1
module ActiveResource
-
1
extend ActiveSupport::Autoload
-
-
1
autoload :Base
-
1
autoload :Connection
-
1
autoload :CustomMethods
-
1
autoload :Formats
-
1
autoload :HttpMock
-
1
autoload :Observing
-
1
autoload :Schema
-
1
autoload :Validations
-
end
-
1
require "active_resource"
-
1
require "rails"
-
-
1
module ActiveResource
-
1
class Railtie < Rails::Railtie
-
1
config.active_resource = ActiveSupport::OrderedOptions.new
-
-
1
initializer "active_resource.set_configs" do |app|
-
app.config.active_resource.each do |k,v|
-
ActiveResource::Base.send "#{k}=", v
-
end
-
end
-
end
-
end
-
1
module ActiveResource
-
1
module VERSION #:nodoc:
-
1
MAJOR = 3
-
1
MINOR = 1
-
1
TINY = 0
-
1
PRE = nil
-
-
1
STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
-
end
-
end
-
1
module ActiveSupport
-
1
if defined? ::BasicObject
-
# A class with no predefined methods that behaves similarly to Builder's
-
# BlankSlate. Used for proxy classes.
-
1
class BasicObject < ::BasicObject
-
1
undef_method :==
-
1
undef_method :equal?
-
-
# Let ActiveSupport::BasicObject at least raise exceptions.
-
1
def raise(*args)
-
::Object.send(:raise, *args)
-
end
-
end
-
else
-
class BasicObject #:nodoc:
-
instance_methods.each do |m|
-
undef_method(m) if m.to_s !~ /(?:^__|^nil\?$|^send$|^object_id$)/
-
end
-
end
-
end
-
end
-
1
class Array
-
# Returns an unique array based on the criteria given as a +Proc+.
-
#
-
# [1, 2, 3, 4].uniq_by { |i| i.odd? } # => [1, 2]
-
#
-
1
def uniq_by
-
hash, array = {}, []
-
each { |i| hash[yield(i)] ||= (array << i) }
-
array
-
end
-
-
# Same as uniq_by, but modifies self.
-
1
def uniq_by!
-
replace(uniq_by{ |i| yield(i) })
-
end
-
end
-
1
require 'active_support/core_ext/class/attribute'
-
1
require 'active_support/core_ext/class/attribute_accessors'
-
1
require 'active_support/core_ext/class/inheritable_attributes'
-
1
require 'active_support/core_ext/class/delegating_attributes'
-
1
require 'active_support/core_ext/class/subclasses'
-
1
require 'active_support/core_ext/kernel/singleton_class'
-
1
require 'active_support/core_ext/module/remove_method'
-
1
require 'active_support/core_ext/array/extract_options'
-
-
1
class Class
-
# Declare a class-level attribute whose value is inheritable by subclasses.
-
# Subclasses can change their own value and it will not impact parent class.
-
#
-
# class Base
-
# class_attribute :setting
-
# end
-
#
-
# class Subclass < Base
-
# end
-
#
-
# Base.setting = true
-
# Subclass.setting # => true
-
# Subclass.setting = false
-
# Subclass.setting # => false
-
# Base.setting # => true
-
#
-
# In the above case as long as Subclass does not assign a value to setting
-
# by performing <tt>Subclass.setting = _something_ </tt>, <tt>Subclass.setting</tt>
-
# would read value assigned to parent class. Once Subclass assigns a value then
-
# the value assigned by Subclass would be returned.
-
#
-
# This matches normal Ruby method inheritance: think of writing an attribute
-
# on a subclass as overriding the reader method. However, you need to be aware
-
# when using +class_attribute+ with mutable structures as +Array+ or +Hash+.
-
# In such cases, you don't want to do changes in places but use setters:
-
#
-
# Base.setting = []
-
# Base.setting # => []
-
# Subclass.setting # => []
-
#
-
# # Appending in child changes both parent and child because it is the same object:
-
# Subclass.setting << :foo
-
# Base.setting # => [:foo]
-
# Subclass.setting # => [:foo]
-
#
-
# # Use setters to not propagate changes:
-
# Base.setting = []
-
# Subclass.setting += [:foo]
-
# Base.setting # => []
-
# Subclass.setting # => [:foo]
-
#
-
# For convenience, a query method is defined as well:
-
#
-
# Subclass.setting? # => false
-
#
-
# Instances may overwrite the class value in the same way:
-
#
-
# Base.setting = true
-
# object = Base.new
-
# object.setting # => true
-
# object.setting = false
-
# object.setting # => false
-
# Base.setting # => true
-
#
-
# To opt out of the instance reader method, pass :instance_reader => false.
-
#
-
# object.setting # => NoMethodError
-
# object.setting? # => NoMethodError
-
#
-
# To opt out of the instance writer method, pass :instance_writer => false.
-
#
-
# object.setting = false # => NoMethodError
-
1
def class_attribute(*attrs)
-
options = attrs.extract_options!
-
instance_reader = options.fetch(:instance_reader, true)
-
instance_writer = options.fetch(:instance_writer, true)
-
-
attrs.each do |name|
-
class_eval <<-RUBY, __FILE__, __LINE__ + 1
-
def self.#{name}() nil end
-
def self.#{name}?() !!#{name} end
-
-
def self.#{name}=(val)
-
singleton_class.class_eval do
-
remove_possible_method(:#{name})
-
define_method(:#{name}) { val }
-
end
-
-
if singleton_class?
-
class_eval do
-
remove_possible_method(:#{name})
-
def #{name}
-
defined?(@#{name}) ? @#{name} : singleton_class.#{name}
-
end
-
end
-
end
-
val
-
end
-
-
if instance_reader
-
remove_possible_method :#{name}
-
def #{name}
-
defined?(@#{name}) ? @#{name} : self.class.#{name}
-
end
-
-
def #{name}?
-
!!#{name}
-
end
-
end
-
RUBY
-
-
attr_writer name if instance_writer
-
end
-
end
-
-
1
private
-
1
def singleton_class?
-
!name || '' == name
-
end
-
end
-
1
require 'active_support/core_ext/object/blank'
-
1
require 'active_support/core_ext/array/extract_options'
-
1
require 'active_support/core_ext/kernel/singleton_class'
-
1
require 'active_support/core_ext/module/remove_method'
-
-
1
class Class
-
1
def superclass_delegating_accessor(name, options = {})
-
# Create private _name and _name= methods that can still be used if the public
-
# methods are overridden. This allows
-
_superclass_delegating_accessor("_#{name}")
-
-
# Generate the public methods name, name=, and name?
-
# These methods dispatch to the private _name, and _name= methods, making them
-
# overridable
-
singleton_class.send(:define_method, name) { send("_#{name}") }
-
singleton_class.send(:define_method, "#{name}?") { !!send("_#{name}") }
-
singleton_class.send(:define_method, "#{name}=") { |value| send("_#{name}=", value) }
-
-
# If an instance_reader is needed, generate methods for name and name= on the
-
# class itself, so instances will be able to see them
-
define_method(name) { send("_#{name}") } if options[:instance_reader] != false
-
define_method("#{name}?") { !!send("#{name}") } if options[:instance_reader] != false
-
end
-
-
1
private
-
-
# Take the object being set and store it in a method. This gives us automatic
-
# inheritance behavior, without having to store the object in an instance
-
# variable and look up the superclass chain manually.
-
1
def _stash_object_in_method(object, method, instance_reader = true)
-
singleton_class.remove_possible_method(method)
-
singleton_class.send(:define_method, method) { object }
-
remove_possible_method(method)
-
define_method(method) { object } if instance_reader
-
end
-
-
1
def _superclass_delegating_accessor(name, options = {})
-
singleton_class.send(:define_method, "#{name}=") do |value|
-
_stash_object_in_method(value, name, options[:instance_reader] != false)
-
end
-
send("#{name}=", nil)
-
end
-
-
end
-
1
require 'active_support/core_ext/object/duplicable'
-
1
require 'active_support/core_ext/array/extract_options'
-
1
require 'active_support/deprecation'
-
-
# Retained for backward compatibility. Methods are now included in Class.
-
1
module ClassInheritableAttributes # :nodoc:
-
1
DEPRECATION_WARNING_MESSAGE = "class_inheritable_attribute is deprecated, please use class_attribute method instead. Notice their behavior are slightly different, so refer to class_attribute documentation first"
-
end
-
-
# It is recommended to use <tt>class_attribute</tt> over methods defined in this file. Please
-
# refer to documentation for <tt>class_attribute</tt> for more information. Officially it is not
-
# deprecated but <tt>class_attribute</tt> is faster.
-
#
-
# Allows attributes to be shared within an inheritance hierarchy. Each descendant gets a copy of
-
# their parents' attributes, instead of just a pointer to the same. This means that the child can add elements
-
# to, for example, an array without those additions being shared with either their parent, siblings, or
-
# children. This is unlike the regular class-level attributes that are shared across the entire hierarchy.
-
#
-
# The copies of inheritable parent attributes are added to subclasses when they are created, via the
-
# +inherited+ hook.
-
#
-
# class Person
-
# class_inheritable_accessor :hair_colors
-
# end
-
#
-
# Person.hair_colors = [:brown, :black, :blonde, :red]
-
# Person.hair_colors # => [:brown, :black, :blonde, :red]
-
# Person.new.hair_colors # => [:brown, :black, :blonde, :red]
-
#
-
# To opt out of the instance writer method, pass :instance_writer => false.
-
# To opt out of the instance reader method, pass :instance_reader => false.
-
#
-
# class Person
-
# class_inheritable_accessor :hair_colors :instance_writer => false, :instance_reader => false
-
# end
-
#
-
# Person.new.hair_colors = [:brown] # => NoMethodError
-
# Person.new.hair_colors # => NoMethodError
-
1
class Class # :nodoc:
-
1
def class_inheritable_reader(*syms)
-
ActiveSupport::Deprecation.warn ClassInheritableAttributes::DEPRECATION_WARNING_MESSAGE
-
options = syms.extract_options!
-
syms.each do |sym|
-
next if sym.is_a?(Hash)
-
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
-
def self.#{sym} # def self.after_add
-
read_inheritable_attribute(:#{sym}) # read_inheritable_attribute(:after_add)
-
end # end
-
#
-
#{" #
-
def #{sym} # def after_add
-
self.class.#{sym} # self.class.after_add
-
end # end
-
" unless options[:instance_reader] == false } # # the reader above is generated unless options[:instance_reader] == false
-
EOS
-
end
-
end
-
-
1
def class_inheritable_writer(*syms)
-
ActiveSupport::Deprecation.warn ClassInheritableAttributes::DEPRECATION_WARNING_MESSAGE
-
options = syms.extract_options!
-
syms.each do |sym|
-
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
-
def self.#{sym}=(obj) # def self.color=(obj)
-
write_inheritable_attribute(:#{sym}, obj) # write_inheritable_attribute(:color, obj)
-
end # end
-
#
-
#{" #
-
def #{sym}=(obj) # def color=(obj)
-
self.class.#{sym} = obj # self.class.color = obj
-
end # end
-
" unless options[:instance_writer] == false } # # the writer above is generated unless options[:instance_writer] == false
-
EOS
-
end
-
end
-
-
1
def class_inheritable_array_writer(*syms)
-
ActiveSupport::Deprecation.warn ClassInheritableAttributes::DEPRECATION_WARNING_MESSAGE
-
options = syms.extract_options!
-
syms.each do |sym|
-
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
-
def self.#{sym}=(obj) # def self.levels=(obj)
-
write_inheritable_array(:#{sym}, obj) # write_inheritable_array(:levels, obj)
-
end # end
-
#
-
#{" #
-
def #{sym}=(obj) # def levels=(obj)
-
self.class.#{sym} = obj # self.class.levels = obj
-
end # end
-
" unless options[:instance_writer] == false } # # the writer above is generated unless options[:instance_writer] == false
-
EOS
-
end
-
end
-
-
1
def class_inheritable_hash_writer(*syms)
-
ActiveSupport::Deprecation.warn ClassInheritableAttributes::DEPRECATION_WARNING_MESSAGE
-
options = syms.extract_options!
-
syms.each do |sym|
-
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
-
def self.#{sym}=(obj) # def self.nicknames=(obj)
-
write_inheritable_hash(:#{sym}, obj) # write_inheritable_hash(:nicknames, obj)
-
end # end
-
#
-
#{" #
-
def #{sym}=(obj) # def nicknames=(obj)
-
self.class.#{sym} = obj # self.class.nicknames = obj
-
end # end
-
" unless options[:instance_writer] == false } # # the writer above is generated unless options[:instance_writer] == false
-
EOS
-
end
-
end
-
-
1
def class_inheritable_accessor(*syms)
-
class_inheritable_reader(*syms)
-
class_inheritable_writer(*syms)
-
end
-
-
1
def class_inheritable_array(*syms)
-
class_inheritable_reader(*syms)
-
class_inheritable_array_writer(*syms)
-
end
-
-
1
def class_inheritable_hash(*syms)
-
class_inheritable_reader(*syms)
-
class_inheritable_hash_writer(*syms)
-
end
-
-
1
def inheritable_attributes
-
140
@inheritable_attributes ||= EMPTY_INHERITABLE_ATTRIBUTES
-
end
-
-
1
def write_inheritable_attribute(key, value)
-
if inheritable_attributes.equal?(EMPTY_INHERITABLE_ATTRIBUTES)
-
@inheritable_attributes = {}
-
end
-
inheritable_attributes[key] = value
-
end
-
-
1
def write_inheritable_array(key, elements)
-
write_inheritable_attribute(key, []) if read_inheritable_attribute(key).nil?
-
write_inheritable_attribute(key, read_inheritable_attribute(key) + elements)
-
end
-
-
1
def write_inheritable_hash(key, hash)
-
write_inheritable_attribute(key, {}) if read_inheritable_attribute(key).nil?
-
write_inheritable_attribute(key, read_inheritable_attribute(key).merge(hash))
-
end
-
-
1
def read_inheritable_attribute(key)
-
inheritable_attributes[key]
-
end
-
-
1
def reset_inheritable_attributes
-
ActiveSupport::Deprecation.warn ClassInheritableAttributes::DEPRECATION_WARNING_MESSAGE
-
@inheritable_attributes = EMPTY_INHERITABLE_ATTRIBUTES
-
end
-
-
1
private
-
# Prevent this constant from being created multiple times
-
1
EMPTY_INHERITABLE_ATTRIBUTES = {}.freeze
-
-
1
def inherited_with_inheritable_attributes(child)
-
140
inherited_without_inheritable_attributes(child) if respond_to?(:inherited_without_inheritable_attributes)
-
-
140
if inheritable_attributes.equal?(EMPTY_INHERITABLE_ATTRIBUTES)
-
140
new_inheritable_attributes = EMPTY_INHERITABLE_ATTRIBUTES
-
else
-
new_inheritable_attributes = Hash[inheritable_attributes.map do |(key, value)|
-
[key, value.duplicable? ? value.dup : value]
-
end]
-
end
-
-
140
child.instance_variable_set('@inheritable_attributes', new_inheritable_attributes)
-
end
-
-
1
alias inherited_without_inheritable_attributes inherited
-
1
alias inherited inherited_with_inheritable_attributes
-
end
-
1
require 'active_support/core_ext/module/anonymous'
-
1
require 'active_support/core_ext/module/reachable'
-
-
1
class Class #:nodoc:
-
1
begin
-
1
ObjectSpace.each_object(Class.new) {}
-
-
1
def descendants
-
descendants = []
-
ObjectSpace.each_object(class << self; self; end) do |k|
-
descendants.unshift k unless k == self
-
end
-
descendants
-
end
-
rescue StandardError # JRuby
-
def descendants
-
descendants = []
-
ObjectSpace.each_object(Class) do |k|
-
descendants.unshift k if k < self
-
end
-
descendants.uniq!
-
descendants
-
end
-
end
-
-
# Returns an array with the direct children of +self+.
-
#
-
# Integer.subclasses # => [Bignum, Fixnum]
-
1
def subclasses
-
subclasses, chain = [], descendants
-
chain.each do |k|
-
subclasses << k unless chain.any? { |c| c > k }
-
end
-
subclasses
-
end
-
end
-
1
require 'date'
-
1
require 'active_support/duration'
-
1
require 'active_support/core_ext/object/acts_like'
-
1
require 'active_support/core_ext/date/zones'
-
1
require 'active_support/core_ext/time/zones'
-
-
1
class Date
-
1
DAYS_INTO_WEEK = { :monday => 0, :tuesday => 1, :wednesday => 2, :thursday => 3, :friday => 4, :saturday => 5, :sunday => 6 }
-
-
1
if RUBY_VERSION < '1.9'
-
undef :>>
-
-
# Backported from 1.9. The one in 1.8 leads to incorrect next_month and
-
# friends for dates where the calendar reform is involved. It additionally
-
# prevents an infinite loop fixed in r27013.
-
def >>(n)
-
y, m = (year * 12 + (mon - 1) + n).divmod(12)
-
m, = (m + 1) .divmod(1)
-
d = mday
-
until jd2 = self.class.valid_civil?(y, m, d, start)
-
d -= 1
-
raise ArgumentError, 'invalid date' unless d > 0
-
end
-
self + (jd2 - jd)
-
end
-
end
-
-
1
class << self
-
# Returns a new Date representing the date 1 day ago (i.e. yesterday's date).
-
1
def yesterday
-
::Date.current.yesterday
-
end
-
-
# Returns a new Date representing the date 1 day after today (i.e. tomorrow's date).
-
1
def tomorrow
-
::Date.current.tomorrow
-
end
-
-
# Returns Time.zone.today when <tt>Time.zone</tt> or <tt>config.time_zone</tt> are set, otherwise just returns Date.today.
-
1
def current
-
::Time.zone ? ::Time.zone.today : ::Date.today
-
end
-
end
-
-
# Returns true if the Date object's date lies in the past. Otherwise returns false.
-
1
def past?
-
self < ::Date.current
-
end
-
-
# Returns true if the Date object's date is today.
-
1
def today?
-
self.to_date == ::Date.current # we need the to_date because of DateTime
-
end
-
-
# Returns true if the Date object's date lies in the future.
-
1
def future?
-
self > ::Date.current
-
end
-
-
# Converts Date to a Time (or DateTime if necessary) with the time portion set to the beginning of the day (0:00)
-
# and then subtracts the specified number of seconds.
-
1
def ago(seconds)
-
to_time_in_current_zone.since(-seconds)
-
end
-
-
# Converts Date to a Time (or DateTime if necessary) with the time portion set to the beginning of the day (0:00)
-
# and then adds the specified number of seconds
-
1
def since(seconds)
-
to_time_in_current_zone.since(seconds)
-
end
-
1
alias :in :since
-
-
# Converts Date to a Time (or DateTime if necessary) with the time portion set to the beginning of the day (0:00)
-
1
def beginning_of_day
-
to_time_in_current_zone
-
end
-
1
alias :midnight :beginning_of_day
-
1
alias :at_midnight :beginning_of_day
-
1
alias :at_beginning_of_day :beginning_of_day
-
-
# Converts Date to a Time (or DateTime if necessary) with the time portion set to the end of the day (23:59:59)
-
1
def end_of_day
-
to_time_in_current_zone.end_of_day
-
end
-
-
1
def plus_with_duration(other) #:nodoc:
-
if ActiveSupport::Duration === other
-
other.since(self)
-
else
-
plus_without_duration(other)
-
end
-
end
-
1
alias_method :plus_without_duration, :+
-
1
alias_method :+, :plus_with_duration
-
-
1
def minus_with_duration(other) #:nodoc:
-
if ActiveSupport::Duration === other
-
plus_with_duration(-other)
-
else
-
minus_without_duration(other)
-
end
-
end
-
1
alias_method :minus_without_duration, :-
-
1
alias_method :-, :minus_with_duration
-
-
# Provides precise Date calculations for years, months, and days. The +options+ parameter takes a hash with
-
# any of these keys: <tt>:years</tt>, <tt>:months</tt>, <tt>:weeks</tt>, <tt>:days</tt>.
-
1
def advance(options)
-
options = options.dup
-
d = self
-
d = d >> options.delete(:years) * 12 if options[:years]
-
d = d >> options.delete(:months) if options[:months]
-
d = d + options.delete(:weeks) * 7 if options[:weeks]
-
d = d + options.delete(:days) if options[:days]
-
d
-
end
-
-
# Returns a new Date where one or more of the elements have been changed according to the +options+ parameter.
-
#
-
# Examples:
-
#
-
# Date.new(2007, 5, 12).change(:day => 1) # => Date.new(2007, 5, 1)
-
# Date.new(2007, 5, 12).change(:year => 2005, :month => 1) # => Date.new(2005, 1, 12)
-
1
def change(options)
-
::Date.new(
-
options[:year] || self.year,
-
options[:month] || self.month,
-
options[:day] || self.day
-
)
-
end
-
-
# Returns a new Date/DateTime representing the time a number of specified weeks ago.
-
1
def weeks_ago(weeks)
-
advance(:weeks => -weeks)
-
end
-
-
# Returns a new Date/DateTime representing the time a number of specified months ago.
-
1
def months_ago(months)
-
advance(:months => -months)
-
end
-
-
# Returns a new Date/DateTime representing the time a number of specified months in the future.
-
1
def months_since(months)
-
advance(:months => months)
-
end
-
-
# Returns a new Date/DateTime representing the time a number of specified years ago.
-
1
def years_ago(years)
-
advance(:years => -years)
-
end
-
-
# Returns a new Date/DateTime representing the time a number of specified years in the future.
-
1
def years_since(years)
-
advance(:years => years)
-
end
-
-
# Shorthand for years_ago(1)
-
def prev_year
-
years_ago(1)
-
1
end unless method_defined?(:prev_year)
-
-
# Shorthand for years_since(1)
-
def next_year
-
years_since(1)
-
1
end unless method_defined?(:next_year)
-
-
# Shorthand for months_ago(1)
-
def prev_month
-
months_ago(1)
-
1
end unless method_defined?(:prev_month)
-
-
# Shorthand for months_since(1)
-
def next_month
-
months_since(1)
-
1
end unless method_defined?(:next_month)
-
-
# Returns a new Date/DateTime representing the "start" of this week (i.e, Monday; DateTime objects will have time set to 0:00).
-
1
def beginning_of_week
-
days_to_monday = self.wday!=0 ? self.wday-1 : 6
-
result = self - days_to_monday
-
self.acts_like?(:time) ? result.midnight : result
-
end
-
1
alias :monday :beginning_of_week
-
1
alias :at_beginning_of_week :beginning_of_week
-
-
# Returns a new Date/DateTime representing the end of this week (Sunday, DateTime objects will have time set to 23:59:59).
-
1
def end_of_week
-
days_to_sunday = self.wday!=0 ? 7-self.wday : 0
-
result = self + days_to_sunday.days
-
self.acts_like?(:time) ? result.end_of_day : result
-
end
-
1
alias :sunday :end_of_week
-
1
alias :at_end_of_week :end_of_week
-
-
# Returns a new Date/DateTime representing the start of the given day in the previous week (default is Monday).
-
1
def prev_week(day = :monday)
-
result = (self - 7).beginning_of_week + DAYS_INTO_WEEK[day]
-
self.acts_like?(:time) ? result.change(:hour => 0) : result
-
end
-
-
# Returns a new Date/DateTime representing the start of the given day in next week (default is Monday).
-
1
def next_week(day = :monday)
-
result = (self + 7).beginning_of_week + DAYS_INTO_WEEK[day]
-
self.acts_like?(:time) ? result.change(:hour => 0) : result
-
end
-
-
# Returns a new ; DateTime objects will have time set to 0:00DateTime representing the start of the month (1st of the month; DateTime objects will have time set to 0:00)
-
1
def beginning_of_month
-
self.acts_like?(:time) ? change(:day => 1, :hour => 0) : change(:day => 1)
-
end
-
1
alias :at_beginning_of_month :beginning_of_month
-
-
# Returns a new Date/DateTime representing the end of the month (last day of the month; DateTime objects will have time set to 0:00)
-
1
def end_of_month
-
last_day = ::Time.days_in_month( self.month, self.year )
-
self.acts_like?(:time) ? change(:day => last_day, :hour => 23, :min => 59, :sec => 59) : change(:day => last_day)
-
end
-
1
alias :at_end_of_month :end_of_month
-
-
# Returns a new Date/DateTime representing the start of the quarter (1st of january, april, july, october; DateTime objects will have time set to 0:00)
-
1
def beginning_of_quarter
-
beginning_of_month.change(:month => [10, 7, 4, 1].detect { |m| m <= self.month })
-
end
-
1
alias :at_beginning_of_quarter :beginning_of_quarter
-
-
# Returns a new Date/DateTime representing the end of the quarter (last day of march, june, september, december; DateTime objects will have time set to 23:59:59)
-
1
def end_of_quarter
-
beginning_of_month.change(:month => [3, 6, 9, 12].detect { |m| m >= self.month }).end_of_month
-
end
-
1
alias :at_end_of_quarter :end_of_quarter
-
-
# Returns a new Date/DateTime representing the start of the year (1st of january; DateTime objects will have time set to 0:00)
-
1
def beginning_of_year
-
self.acts_like?(:time) ? change(:month => 1, :day => 1, :hour => 0) : change(:month => 1, :day => 1)
-
end
-
1
alias :at_beginning_of_year :beginning_of_year
-
-
# Returns a new Time representing the end of the year (31st of december; DateTime objects will have time set to 23:59:59)
-
1
def end_of_year
-
self.acts_like?(:time) ? change(:month => 12, :day => 31, :hour => 23, :min => 59, :sec => 59) : change(:month => 12, :day => 31)
-
end
-
1
alias :at_end_of_year :end_of_year
-
-
# Convenience method which returns a new Date/DateTime representing the time 1 day ago
-
1
def yesterday
-
self - 1
-
end
-
-
# Convenience method which returns a new Date/DateTime representing the time 1 day since the instance time
-
1
def tomorrow
-
self + 1
-
end
-
end
-
1
require 'date'
-
1
require 'active_support/core_ext/time/zones'
-
-
1
class Date
-
# Converts Date to a TimeWithZone in the current zone if Time.zone or Time.zone_default
-
# is set, otherwise converts Date to a Time via Date#to_time
-
1
def to_time_in_current_zone
-
if ::Time.zone
-
::Time.zone.local(year, month, day)
-
else
-
to_time
-
end
-
end
-
end
-
1
require 'rational' unless RUBY_VERSION >= '1.9.2'
-
-
1
class DateTime
-
1
class << self
-
# DateTimes aren't aware of DST rules, so use a consistent non-DST offset when creating a DateTime with an offset in the local zone
-
1
def local_offset
-
::Time.local(2007).utc_offset.to_r / 86400
-
end
-
-
# Returns <tt>Time.zone.now.to_datetime</tt> when <tt>Time.zone</tt> or <tt>config.time_zone</tt> are set, otherwise returns <tt>Time.now.to_datetime</tt>.
-
1
def current
-
::Time.zone ? ::Time.zone.now.to_datetime : ::Time.now.to_datetime
-
end
-
end
-
-
# Tells whether the DateTime object's datetime lies in the past
-
1
def past?
-
self < ::DateTime.current
-
end
-
-
# Tells whether the DateTime object's datetime lies in the future
-
1
def future?
-
self > ::DateTime.current
-
end
-
-
# Seconds since midnight: DateTime.now.seconds_since_midnight
-
1
def seconds_since_midnight
-
sec + (min * 60) + (hour * 3600)
-
end
-
-
# Returns a new DateTime where one or more of the elements have been changed according to the +options+ parameter. The time options
-
# (hour, minute, sec) reset cascadingly, so if only the hour is passed, then minute and sec is set to 0. If the hour and
-
# minute is passed, then sec is set to 0.
-
1
def change(options)
-
::DateTime.civil(
-
options[:year] || year,
-
options[:month] || month,
-
options[:day] || day,
-
options[:hour] || hour,
-
options[:min] || (options[:hour] ? 0 : min),
-
options[:sec] || ((options[:hour] || options[:min]) ? 0 : sec),
-
options[:offset] || offset,
-
options[:start] || start
-
)
-
end
-
-
# Uses Date to provide precise Time calculations for years, months, and days.
-
# The +options+ parameter takes a hash with any of these keys: <tt>:years</tt>,
-
# <tt>:months</tt>, <tt>:weeks</tt>, <tt>:days</tt>, <tt>:hours</tt>,
-
# <tt>:minutes</tt>, <tt>:seconds</tt>.
-
1
def advance(options)
-
d = to_date.advance(options)
-
datetime_advanced_by_date = change(:year => d.year, :month => d.month, :day => d.day)
-
seconds_to_advance = (options[:seconds] || 0) + (options[:minutes] || 0) * 60 + (options[:hours] || 0) * 3600
-
seconds_to_advance == 0 ? datetime_advanced_by_date : datetime_advanced_by_date.since(seconds_to_advance)
-
end
-
-
# Returns a new DateTime representing the time a number of seconds ago
-
# Do not use this method in combination with x.months, use months_ago instead!
-
1
def ago(seconds)
-
since(-seconds)
-
end
-
-
# Returns a new DateTime representing the time a number of seconds since the instance time
-
# Do not use this method in combination with x.months, use months_since instead!
-
1
def since(seconds)
-
self + Rational(seconds.round, 86400)
-
end
-
1
alias :in :since
-
-
# Returns a new DateTime representing the start of the day (0:00)
-
1
def beginning_of_day
-
change(:hour => 0)
-
end
-
1
alias :midnight :beginning_of_day
-
1
alias :at_midnight :beginning_of_day
-
1
alias :at_beginning_of_day :beginning_of_day
-
-
# Returns a new DateTime representing the end of the day (23:59:59)
-
1
def end_of_day
-
change(:hour => 23, :min => 59, :sec => 59)
-
end
-
-
# 1.9.3 defines + and - on DateTime, < 1.9.3 do not.
-
1
if DateTime.public_instance_methods(false).include?(:+)
-
def plus_with_duration(other) #:nodoc:
-
if ActiveSupport::Duration === other
-
other.since(self)
-
else
-
plus_without_duration(other)
-
end
-
end
-
alias_method :plus_without_duration, :+
-
alias_method :+, :plus_with_duration
-
-
def minus_with_duration(other) #:nodoc:
-
if ActiveSupport::Duration === other
-
plus_with_duration(-other)
-
else
-
minus_without_duration(other)
-
end
-
end
-
alias_method :minus_without_duration, :-
-
alias_method :-, :minus_with_duration
-
end
-
-
# Adjusts DateTime to UTC by adding its offset value; offset is set to 0
-
#
-
# Example:
-
#
-
# DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-6, 24)) # => Mon, 21 Feb 2005 10:11:12 -0600
-
# DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-6, 24)).utc # => Mon, 21 Feb 2005 16:11:12 +0000
-
1
def utc
-
new_offset(0)
-
end
-
1
alias_method :getutc, :utc
-
-
# Returns true if offset == 0
-
1
def utc?
-
offset == 0
-
end
-
-
# Returns the offset value in seconds
-
1
def utc_offset
-
(offset * 86400).to_i
-
end
-
-
# Layers additional behavior on DateTime#<=> so that Time and ActiveSupport::TimeWithZone instances can be compared with a DateTime
-
1
def <=>(other)
-
super other.to_datetime
-
end
-
end
-
1
require 'active_support/inflector/methods'
-
1
require 'active_support/core_ext/time/conversions'
-
1
require 'active_support/core_ext/date_time/calculations'
-
1
require 'active_support/values/time_zone'
-
-
1
class DateTime
-
# Ruby 1.9 has DateTime#to_time which internally relies on Time. We define our own #to_time which allows
-
# DateTimes outside the range of what can be created with Time.
-
1
remove_method :to_time if instance_methods.include?(:to_time)
-
-
# Convert to a formatted string. See Time::DATE_FORMATS for predefined formats.
-
#
-
# This method is aliased to <tt>to_s</tt>.
-
#
-
# === Examples
-
# datetime = DateTime.civil(2007, 12, 4, 0, 0, 0, 0) # => Tue, 04 Dec 2007 00:00:00 +0000
-
#
-
# datetime.to_formatted_s(:db) # => "2007-12-04 00:00:00"
-
# datetime.to_s(:db) # => "2007-12-04 00:00:00"
-
# datetime.to_s(:number) # => "20071204000000"
-
# datetime.to_formatted_s(:short) # => "04 Dec 00:00"
-
# datetime.to_formatted_s(:long) # => "December 04, 2007 00:00"
-
# datetime.to_formatted_s(:long_ordinal) # => "December 4th, 2007 00:00"
-
# datetime.to_formatted_s(:rfc822) # => "Tue, 04 Dec 2007 00:00:00 +0000"
-
#
-
# == Adding your own datetime formats to to_formatted_s
-
# DateTime formats are shared with Time. You can add your own to the
-
# Time::DATE_FORMATS hash. Use the format name as the hash key and
-
# either a strftime string or Proc instance that takes a time or
-
# datetime argument as the value.
-
#
-
# # config/initializers/time_formats.rb
-
# Time::DATE_FORMATS[:month_and_year] = "%B %Y"
-
# Time::DATE_FORMATS[:short_ordinal] = lambda { |time| time.strftime("%B #{time.day.ordinalize}") }
-
1
def to_formatted_s(format = :default)
-
if formatter = ::Time::DATE_FORMATS[format]
-
formatter.respond_to?(:call) ? formatter.call(self).to_s : strftime(formatter)
-
else
-
to_default_s
-
end
-
end
-
1
alias_method :to_default_s, :to_s unless (instance_methods(false) & [:to_s, 'to_s']).empty?
-
1
alias_method :to_s, :to_formatted_s
-
-
# Returns the +utc_offset+ as an +HH:MM formatted string. Examples:
-
#
-
# datetime = DateTime.civil(2000, 1, 1, 0, 0, 0, Rational(-6, 24))
-
# datetime.formatted_offset # => "-06:00"
-
# datetime.formatted_offset(false) # => "-0600"
-
1
def formatted_offset(colon = true, alternate_utc_string = nil)
-
utc? && alternate_utc_string || ActiveSupport::TimeZone.seconds_to_utc_offset(utc_offset, colon)
-
end
-
-
# Overrides the default inspect method with a human readable one, e.g., "Mon, 21 Feb 2005 14:30:00 +0000".
-
1
def readable_inspect
-
to_s(:rfc822)
-
end
-
1
alias_method :default_inspect, :inspect
-
1
alias_method :inspect, :readable_inspect
-
-
# Converts self to a Ruby Date object; time portion is discarded.
-
def to_date
-
::Date.new(year, month, day)
-
1
end unless instance_methods(false).include?(:to_date)
-
-
# Attempts to convert self to a Ruby Time object; returns self if out of range of Ruby Time class.
-
# If self has an offset other than 0, self will just be returned unaltered, since there's no clean way to map it to a Time.
-
1
def to_time
-
self.offset == 0 ? ::Time.utc_time(year, month, day, hour, min, sec, sec_fraction * (RUBY_VERSION < '1.9' ? 86400000000 : 1000000)) : self
-
end
-
-
# To be able to keep Times, Dates and DateTimes interchangeable on conversions.
-
def to_datetime
-
self
-
1
end unless instance_methods(false).include?(:to_datetime)
-
-
1
def self.civil_from_format(utc_or_local, year, month=1, day=1, hour=0, min=0, sec=0)
-
offset = utc_or_local.to_sym == :local ? local_offset : 0
-
civil(year, month, day, hour, min, sec, offset)
-
end
-
-
# Converts datetime to an appropriate format for use in XML.
-
def xmlschema
-
strftime("%Y-%m-%dT%H:%M:%S%Z")
-
1
end unless instance_methods(false).include?(:xmlschema)
-
-
# Converts self to a floating-point number of seconds since the Unix epoch.
-
1
def to_f
-
seconds_since_unix_epoch.to_f
-
end
-
-
# Converts self to an integer number of seconds since the Unix epoch.
-
1
def to_i
-
seconds_since_unix_epoch.to_i
-
end
-
-
1
private
-
-
1
def seconds_since_unix_epoch
-
seconds_per_day = 86_400
-
(self - ::DateTime.civil(1970)) * seconds_per_day
-
end
-
end
-
1
require 'active_support/ordered_hash'
-
-
1
module Enumerable
-
# Ruby 1.8.7 introduces group_by, but the result isn't ordered. Override it.
-
1
remove_method(:group_by) if [].respond_to?(:group_by) && RUBY_VERSION < '1.9'
-
-
# Collect an enumerable into sets, grouped by the result of a block. Useful,
-
# for example, for grouping records by date.
-
#
-
# Example:
-
#
-
# latest_transcripts.group_by(&:day).each do |day, transcripts|
-
# p "#{day} -> #{transcripts.map(&:class).join(', ')}"
-
# end
-
# "2006-03-01 -> Transcript"
-
# "2006-02-28 -> Transcript"
-
# "2006-02-27 -> Transcript, Transcript"
-
# "2006-02-26 -> Transcript, Transcript"
-
# "2006-02-25 -> Transcript"
-
# "2006-02-24 -> Transcript, Transcript"
-
# "2006-02-23 -> Transcript"
-
def group_by
-
assoc = ActiveSupport::OrderedHash.new
-
-
each do |element|
-
key = yield(element)
-
-
if assoc.has_key?(key)
-
assoc[key] << element
-
else
-
assoc[key] = [element]
-
end
-
end
-
-
assoc
-
1
end unless [].respond_to?(:group_by)
-
-
# Calculates a sum from the elements. Examples:
-
#
-
# payments.sum { |p| p.price * p.tax_rate }
-
# payments.sum(&:price)
-
#
-
# The latter is a shortcut for:
-
#
-
# payments.inject(0) { |sum, p| sum + p.price }
-
#
-
# It can also calculate the sum without the use of a block.
-
#
-
# [5, 15, 10].sum # => 30
-
# ["foo", "bar"].sum # => "foobar"
-
# [[1, 2], [3, 1, 5]].sum => [1, 2, 3, 1, 5]
-
#
-
# The default sum of an empty list is zero. You can override this default:
-
#
-
# [].sum(Payment.new(0)) { |i| i.amount } # => Payment.new(0)
-
#
-
1
def sum(identity = 0, &block)
-
if block_given?
-
map(&block).sum(identity)
-
else
-
inject { |sum, element| sum + element } || identity
-
end
-
end
-
-
# Iterates over a collection, passing the current element *and* the
-
# +memo+ to the block. Handy for building up hashes or
-
# reducing collections down to one object. Examples:
-
#
-
# %w(foo bar).each_with_object({}) { |str, hsh| hsh[str] = str.upcase }
-
# # => {'foo' => 'FOO', 'bar' => 'BAR'}
-
#
-
# *Note* that you can't use immutable objects like numbers, true or false as
-
# the memo. You would think the following returns 120, but since the memo is
-
# never changed, it does not.
-
#
-
# (1..5).each_with_object(1) { |value, memo| memo *= value } # => 1
-
#
-
def each_with_object(memo, &block)
-
each do |element|
-
block.call(element, memo)
-
end
-
memo
-
1
end unless [].respond_to?(:each_with_object)
-
-
# Convert an enumerable to a hash. Examples:
-
#
-
# people.index_by(&:login)
-
# => { "nextangle" => <Person ...>, "chade-" => <Person ...>, ...}
-
# people.index_by { |person| "#{person.first_name} #{person.last_name}" }
-
# => { "Chade- Fowlersburg-e" => <Person ...>, "David Heinemeier Hansson" => <Person ...>, ...}
-
#
-
1
def index_by
-
Hash[map { |elem| [yield(elem), elem] }]
-
end
-
-
# Returns true if the collection has more than 1 element. Functionally equivalent to collection.size > 1.
-
# Can be called with a block too, much like any?, so people.many? { |p| p.age > 26 } returns true if more than 1 person is over 26.
-
1
def many?(&block)
-
size = block_given? ? count(&block) : self.size
-
size > 1
-
end
-
-
# The negative of the Enumerable#include?. Returns true if the collection does not include the object.
-
1
def exclude?(object)
-
!include?(object)
-
end
-
end
-
-
1
class Range #:nodoc:
-
# Optimize range sum to use arithmetic progression if a block is not given and
-
# we have a range of numeric values.
-
1
def sum(identity = 0)
-
return super if block_given? || !(first.instance_of?(Integer) && last.instance_of?(Integer))
-
actual_last = exclude_end? ? (last - 1) : last
-
(actual_last - first + 1) * (actual_last + first) / 2
-
end
-
end
-
1
class File
-
1
unless File.allocate.respond_to?(:to_path)
-
alias to_path path
-
end
-
end
-
1
module Kernel
-
# Returns the object's singleton class.
-
def singleton_class
-
class << self
-
self
-
end
-
1
end unless respond_to?(:singleton_class) # exists in 1.9.2
-
-
# class_eval on an object acts like singleton_class.class_eval.
-
1
def class_eval(*args, &block)
-
singleton_class.class_eval(*args, &block)
-
end
-
end
-
1
class LoadError
-
1
REGEXPS = [
-
/^no such file to load -- (.+)$/i,
-
/^Missing \w+ (?:file\s*)?([^\s]+.rb)$/i,
-
/^Missing API definition file in (.+)$/i,
-
/^cannot load such file -- (.+)$/i,
-
]
-
-
1
def path
-
@path ||= begin
-
REGEXPS.find do |regex|
-
message =~ regex
-
end
-
$1
-
end
-
end
-
-
1
def is_missing?(location)
-
location.sub(/\.rb$/, '') == path.sub(/\.rb$/, '')
-
end
-
end
-
-
1
MissingSourceFile = LoadError
-
1
require 'active_support/core_ext/object/blank'
-
-
1
class Module
-
# A module may or may not have a name.
-
#
-
# module M; end
-
# M.name # => "M"
-
#
-
# m = Module.new
-
# m.name # => ""
-
#
-
# A module gets a name when it is first assigned to a constant. Either
-
# via the +module+ or +class+ keyword or by an explicit assignment:
-
#
-
# m = Module.new # creates an anonymous module
-
# M = m # => m gets a name here as a side-effect
-
# m.name # => "M"
-
#
-
1
def anonymous?
-
# Uses blank? because the name of an anonymous class is an empty
-
# string in 1.8, and nil in 1.9.
-
name.blank?
-
end
-
end
-
1
class Module
-
# Declares an attribute reader backed by an internally-named instance variable.
-
1
def attr_internal_reader(*attrs)
-
attrs.each {|attr_name| attr_internal_define(attr_name, :reader)}
-
end
-
-
# Declares an attribute writer backed by an internally-named instance variable.
-
1
def attr_internal_writer(*attrs)
-
attrs.each {|attr_name| attr_internal_define(attr_name, :writer)}
-
end
-
-
# Declares an attribute reader and writer backed by an internally-named instance
-
# variable.
-
1
def attr_internal_accessor(*attrs)
-
attr_internal_reader(*attrs)
-
attr_internal_writer(*attrs)
-
end
-
-
1
alias_method :attr_internal, :attr_internal_accessor
-
-
2
class << self; attr_accessor :attr_internal_naming_format end
-
1
self.attr_internal_naming_format = '@_%s'
-
-
1
private
-
1
def attr_internal_ivar_name(attr)
-
Module.attr_internal_naming_format % attr
-
end
-
-
1
def attr_internal_define(attr_name, type)
-
internal_name = attr_internal_ivar_name(attr_name).sub(/\A@/, '')
-
class_eval do # class_eval is necessary on 1.9 or else the methods a made private
-
# use native attr_* methods as they are faster on some Ruby implementations
-
send("attr_#{type}", internal_name)
-
end
-
attr_name, internal_name = "#{attr_name}=", "#{internal_name}=" if type == :writer
-
alias_method attr_name, internal_name
-
remove_method internal_name
-
end
-
end
-
1
class Module
-
1
if instance_methods[0].is_a?(Symbol)
-
1
def instance_method_names(*args)
-
instance_methods(*args).map(&:to_s)
-
end
-
-
1
def method_names(*args)
-
methods(*args).map(&:to_s)
-
end
-
else
-
alias_method :instance_method_names, :instance_methods
-
alias_method :method_names, :methods
-
end
-
end
-
1
require 'active_support/core_ext/module/anonymous'
-
1
require 'active_support/core_ext/string/inflections'
-
-
1
class Module
-
1
def reachable? #:nodoc:
-
!anonymous? && name.constantize.equal?(self)
-
rescue NameError
-
false
-
end
-
end
-
1
class NameError
-
# Extract the name of the missing constant from the exception message.
-
1
def missing_name
-
if /undefined local variable or method/ !~ message
-
$1 if /((::)?([A-Z]\w*)(::[A-Z]\w*)*)$/ =~ message
-
end
-
end
-
-
# Was this exception raised because the given name was missing?
-
1
def missing_name?(name)
-
if name.is_a? Symbol
-
last_name = (missing_name || '').split('::').last
-
last_name == name.to_s
-
else
-
missing_name == name.to_s
-
end
-
end
-
end
-
1
class Object
-
# A duck-type assistant method. For example, Active Support extends Date
-
# to define an acts_like_date? method, and extends Time to define
-
# acts_like_time?. As a result, we can do "x.acts_like?(:time)" and
-
# "x.acts_like?(:date)" to do duck-type-safe comparisons, since classes that
-
# we want to act like Time simply need to define an acts_like_time? method.
-
1
def acts_like?(duck)
-
respond_to? :"acts_like_#{duck}?"
-
end
-
end
-
#--
-
# Most objects are cloneable, but not all. For example you can't dup +nil+:
-
#
-
# nil.dup # => TypeError: can't dup NilClass
-
#
-
# Classes may signal their instances are not duplicable removing +dup+/+clone+
-
# or raising exceptions from them. So, to dup an arbitrary object you normally
-
# use an optimistic approach and are ready to catch an exception, say:
-
#
-
# arbitrary_object.dup rescue object
-
#
-
# Rails dups objects in a few critical spots where they are not that arbitrary.
-
# That rescue is very expensive (like 40 times slower than a predicate), and it
-
# is often triggered.
-
#
-
# That's why we hardcode the following cases and check duplicable? instead of
-
# using that rescue idiom.
-
#++
-
1
class Object
-
# Can you safely dup this object?
-
#
-
# False for +nil+, +false+, +true+, symbols, numbers, class and module objects;
-
# true otherwise.
-
1
def duplicable?
-
true
-
end
-
end
-
-
1
class NilClass
-
# +nil+ is not duplicable:
-
#
-
# nil.duplicable? # => false
-
# nil.dup # => TypeError: can't dup NilClass
-
#
-
1
def duplicable?
-
false
-
end
-
end
-
-
1
class FalseClass
-
# +false+ is not duplicable:
-
#
-
# false.duplicable? # => false
-
# false.dup # => TypeError: can't dup FalseClass
-
#
-
1
def duplicable?
-
false
-
end
-
end
-
-
1
class TrueClass
-
# +true+ is not duplicable:
-
#
-
# true.duplicable? # => false
-
# true.dup # => TypeError: can't dup TrueClass
-
#
-
1
def duplicable?
-
false
-
end
-
end
-
-
1
class Symbol
-
# Symbols are not duplicable:
-
#
-
# :my_symbol.duplicable? # => false
-
# :my_symbol.dup # => TypeError: can't dup Symbol
-
#
-
1
def duplicable?
-
false
-
end
-
end
-
-
1
class Numeric
-
# Numbers are not duplicable:
-
#
-
# 3.duplicable? # => false
-
# 3.dup # => TypeError: can't dup Fixnum
-
#
-
1
def duplicable?
-
false
-
end
-
end
-
-
1
class Class
-
# Classes are not duplicable:
-
#
-
# c = Class.new # => #<Class:0x10328fd80>
-
# c.dup # => #<Class:0x10328fd80>
-
#
-
# Note +dup+ returned the same class object.
-
1
def duplicable?
-
false
-
end
-
end
-
-
1
class Module
-
# Modules are not duplicable:
-
#
-
# m = Module.new # => #<Module:0x10328b6e0>
-
# m.dup # => #<Module:0x10328b6e0>
-
#
-
# Note +dup+ returned the same module object.
-
1
def duplicable?
-
false
-
end
-
end
-
1
class Object
-
# Returns true if this object is included in the argument. Argument must be
-
# any object which responds to +#include?+. Usage:
-
#
-
# characters = ["Konata", "Kagami", "Tsukasa"]
-
# "Konata".in?(characters) # => true
-
#
-
# This will throw an ArgumentError if the argument doesn't respond
-
# to +#include?+.
-
1
def in?(another_object)
-
another_object.include?(self)
-
rescue NoMethodError
-
raise ArgumentError.new("The parameter passed to #in? must respond to #include?")
-
end
-
end
-
1
class Object
-
# Invokes the method identified by the symbol +method+, passing it any arguments
-
# and/or the block specified, just like the regular Ruby <tt>Object#send</tt> does.
-
#
-
# *Unlike* that method however, a +NoMethodError+ exception will *not* be raised
-
# and +nil+ will be returned instead, if the receiving object is a +nil+ object or NilClass.
-
#
-
# If try is called without a method to call, it will yield any given block with the object.
-
#
-
# ==== Examples
-
#
-
# Without +try+
-
# @person && @person.name
-
# or
-
# @person ? @person.name : nil
-
#
-
# With +try+
-
# @person.try(:name)
-
#
-
# +try+ also accepts arguments and/or a block, for the method it is trying
-
# Person.try(:find, 1)
-
# @people.try(:collect) {|p| p.name}
-
#
-
# Without a method argument try will yield to the block unless the receiver is nil.
-
# @person.try { |p| "#{p.first_name} #{p.last_name}" }
-
#--
-
# +try+ behaves like +Object#send+, unless called on +NilClass+.
-
1
def try(*a, &b)
-
if a.empty? && block_given?
-
yield self
-
elsif !a.empty? && !respond_to?(a.first)
-
nil
-
else
-
__send__(*a, &b)
-
end
-
end
-
end
-
-
1
class NilClass
-
# Calling +try+ on +nil+ always returns +nil+.
-
# It becomes specially helpful when navigating through associations that may return +nil+.
-
#
-
# === Examples
-
#
-
# nil.try(:name) # => nil
-
#
-
# Without +try+
-
# @person && !@person.children.blank? && @person.children.first.name
-
#
-
# With +try+
-
# @person.try(:children).try(:first).try(:name)
-
1
def try(*args)
-
nil
-
end
-
end
-
1
module Process
-
def self.daemon(nochdir = nil, noclose = nil)
-
exit if fork # Parent exits, child continues.
-
Process.setsid # Become session leader.
-
exit if fork # Zap session leader. See [1].
-
-
unless nochdir
-
Dir.chdir "/" # Release old working directory.
-
end
-
-
File.umask 0000 # Ensure sensible umask. Adjust as needed.
-
-
unless noclose
-
STDIN.reopen "/dev/null" # Free file descriptors and
-
STDOUT.reopen "/dev/null", "a" # point them somewhere sensible.
-
STDERR.reopen '/dev/null', 'a'
-
end
-
-
trap("TERM") { exit }
-
-
return 0
-
1
end unless respond_to?(:daemon)
-
end
-
1
require 'active_support/core_ext/kernel/reporting'
-
-
# Fixes the rexml vulnerability disclosed at:
-
# http://www.ruby-lang.org/en/news/2008/08/23/dos-vulnerability-in-rexml/
-
# This fix is identical to rexml-expansion-fix version 1.0.1.
-
#
-
# We still need to distribute this fix because albeit the REXML
-
# in recent 1.8.7s is patched, it wasn't in early patchlevels.
-
1
require 'rexml/rexml'
-
-
# Earlier versions of rexml defined REXML::Version, newer ones REXML::VERSION
-
1
unless (defined?(REXML::VERSION) ? REXML::VERSION : REXML::Version) > "3.1.7.2"
-
silence_warnings { require 'rexml/document' }
-
-
# REXML in 1.8.7 has the patch but early patchlevels didn't update Version from 3.1.7.2.
-
unless REXML::Document.respond_to?(:entity_expansion_limit=)
-
silence_warnings { require 'rexml/entity' }
-
-
module REXML #:nodoc:
-
class Entity < Child #:nodoc:
-
undef_method :unnormalized
-
def unnormalized
-
document.record_entity_expansion! if document
-
v = value()
-
return nil if v.nil?
-
@unnormalized = Text::unnormalize(v, parent)
-
@unnormalized
-
end
-
end
-
class Document < Element #:nodoc:
-
@@entity_expansion_limit = 10_000
-
def self.entity_expansion_limit= val
-
@@entity_expansion_limit = val
-
end
-
-
def record_entity_expansion!
-
@number_of_expansions ||= 0
-
@number_of_expansions += 1
-
if @number_of_expansions > @@entity_expansion_limit
-
raise "Number of entity expansions exceeded, processing aborted."
-
end
-
end
-
end
-
end
-
end
-
end
-
# encoding: utf-8
-
1
require 'date'
-
1
require 'active_support/core_ext/time/publicize_conversion_methods'
-
1
require 'active_support/core_ext/time/calculations'
-
-
1
class String
-
# Returns the codepoint of the first character of the string, assuming a
-
# single-byte character encoding:
-
#
-
# "a".ord # => 97
-
# "Ã ".ord # => 224, in ISO-8859-1
-
#
-
# This method is defined in Ruby 1.8 for Ruby 1.9 forward compatibility on
-
# these character encodings.
-
#
-
# <tt>ActiveSupport::Multibyte::Chars#ord</tt> is forward compatible with
-
# Ruby 1.9 on UTF8 strings:
-
#
-
# "a".mb_chars.ord # => 97
-
# "Ã ".mb_chars.ord # => 224, in UTF8
-
#
-
# Note that the 224 is different in both examples. In ISO-8859-1 "Ã " is
-
# represented as a single byte, 224. In UTF8 it is represented with two
-
# bytes, namely 195 and 160, but its Unicode codepoint is 224. If we
-
# call +ord+ on the UTF8 string "Ã " the return value will be 195. That is
-
# not an error, because UTF8 is unsupported, the call itself would be
-
# bogus.
-
def ord
-
self[0]
-
1
end unless method_defined?(:ord)
-
-
# +getbyte+ backport from Ruby 1.9
-
1
alias_method :getbyte, :[] unless method_defined?(:getbyte)
-
-
# Form can be either :utc (default) or :local.
-
1
def to_time(form = :utc)
-
return nil if self.blank?
-
d = ::Date._parse(self, false).values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction).map { |arg| arg || 0 }
-
d[6] *= 1000000
-
::Time.send("#{form}_time", *d)
-
end
-
-
1
def to_date
-
return nil if self.blank?
-
::Date.new(*::Date._parse(self, false).values_at(:year, :mon, :mday))
-
end
-
-
1
def to_datetime
-
return nil if self.blank?
-
d = ::Date._parse(self, false).values_at(:year, :mon, :mday, :hour, :min, :sec, :zone, :sec_fraction).map { |arg| arg || 0 }
-
d[5] += d.pop
-
::DateTime.civil(*d)
-
end
-
end
-
1
class String
-
1
if defined?(Encoding) && "".respond_to?(:encode)
-
1
def encoding_aware?
-
true
-
end
-
else
-
def encoding_aware?
-
false
-
end
-
end
-
end
-
1
require 'active_support/i18n'
-
1
require 'i18n/core_ext/string/interpolate'
-
1
require 'erb'
-
1
require 'active_support/core_ext/kernel/singleton_class'
-
-
1
class ERB
-
1
module Util
-
1
HTML_ESCAPE = { '&' => '&', '>' => '>', '<' => '<', '"' => '"' }
-
1
JSON_ESCAPE = { '&' => '\u0026', '>' => '\u003E', '<' => '\u003C' }
-
-
# A utility method for escaping HTML tag characters.
-
# This method is also aliased as <tt>h</tt>.
-
#
-
# In your ERB templates, use this method to escape any unsafe content. For example:
-
# <%=h @person.name %>
-
#
-
# ==== Example:
-
# puts html_escape("is a > 0 & a < 10?")
-
# # => is a > 0 & a < 10?
-
1
def html_escape(s)
-
s = s.to_s
-
if s.html_safe?
-
s
-
else
-
s.to_s.gsub(/&/, "&").gsub(/\"/, """).gsub(/>/, ">").gsub(/</, "<").html_safe
-
end
-
end
-
-
# Aliasing twice issues a warning "discarding old...". Remove first to avoid it.
-
1
remove_method(:h)
-
1
alias h html_escape
-
-
1
module_function :h
-
-
1
singleton_class.send(:remove_method, :html_escape)
-
1
module_function :html_escape
-
-
# A utility method for escaping HTML entities in JSON strings
-
# using \uXXXX JavaScript escape sequences for string literals:
-
#
-
# json_escape("is a > 0 & a < 10?")
-
# # => is a \u003E 0 \u0026 a \u003C 10?
-
#
-
# Note that after this operation is performed the output is not
-
# valid JSON. In particular double quotes are removed:
-
#
-
# json_escape('{"name":"john","created_at":"2010-04-28T01:39:31Z","id":1}')
-
# # => {name:john,created_at:2010-04-28T01:39:31Z,id:1}
-
#
-
# This method is also aliased as +j+, and available as a helper
-
# in Rails templates:
-
#
-
# <%=j @person.to_json %>
-
#
-
1
def json_escape(s)
-
result = s.to_s.gsub(/[&"><]/) { |special| JSON_ESCAPE[special] }
-
s.html_safe? ? result.html_safe : result
-
end
-
-
1
alias j json_escape
-
1
module_function :j
-
1
module_function :json_escape
-
end
-
end
-
-
1
class Object
-
1
def html_safe?
-
false
-
end
-
end
-
-
1
class Numeric
-
1
def html_safe?
-
true
-
end
-
end
-
-
1
module ActiveSupport #:nodoc:
-
1
class SafeBuffer < String
-
1
UNSAFE_STRING_METHODS = ["capitalize", "chomp", "chop", "delete", "downcase", "gsub", "lstrip", "next", "reverse", "rstrip", "slice", "squeeze", "strip", "sub", "succ", "swapcase", "tr", "tr_s", "upcase"].freeze
-
-
1
alias_method :original_concat, :concat
-
1
private :original_concat
-
-
1
class SafeConcatError < StandardError
-
1
def initialize
-
super "Could not concatenate to the buffer because it is not html safe."
-
end
-
end
-
-
1
def safe_concat(value)
-
raise SafeConcatError if dirty?
-
original_concat(value)
-
end
-
-
1
def initialize(*)
-
@dirty = false
-
super
-
end
-
-
1
def initialize_copy(other)
-
super
-
@dirty = other.dirty?
-
end
-
-
1
def concat(value)
-
if dirty? || value.html_safe?
-
super(value)
-
else
-
super(ERB::Util.h(value))
-
end
-
end
-
1
alias << concat
-
-
1
def +(other)
-
dup.concat(other)
-
end
-
-
1
def html_safe?
-
!dirty?
-
end
-
-
1
def to_s
-
self
-
end
-
-
1
def to_param
-
to_str
-
end
-
-
1
def encode_with(coder)
-
coder.represent_scalar nil, to_str
-
end
-
-
1
def to_yaml(*args)
-
return super() if defined?(YAML::ENGINE) && !YAML::ENGINE.syck?
-
to_str.to_yaml(*args)
-
end
-
-
1
for unsafe_method in UNSAFE_STRING_METHODS
-
19
class_eval <<-EOT, __FILE__, __LINE__
-
def #{unsafe_method}(*args)
-
super.to_str
-
end
-
-
def #{unsafe_method}!(*args)
-
@dirty = true
-
super
-
end
-
EOT
-
end
-
-
1
protected
-
-
1
def dirty?
-
@dirty
-
end
-
end
-
end
-
-
1
class String
-
1
def html_safe
-
ActiveSupport::SafeBuffer.new(self)
-
end
-
end
-
1
require 'active_support/duration'
-
1
require 'active_support/core_ext/time/zones'
-
1
require 'active_support/core_ext/time/conversions'
-
-
1
class Time
-
1
COMMON_YEAR_DAYS_IN_MONTH = [nil, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
-
1
DAYS_INTO_WEEK = { :monday => 0, :tuesday => 1, :wednesday => 2, :thursday => 3, :friday => 4, :saturday => 5, :sunday => 6 }
-
-
1
class << self
-
# Overriding case equality method so that it returns true for ActiveSupport::TimeWithZone instances
-
1
def ===(other)
-
other.is_a?(::Time)
-
end
-
-
# Return the number of days in the given month.
-
# If no year is specified, it will use the current year.
-
1
def days_in_month(month, year = now.year)
-
return 29 if month == 2 && ::Date.gregorian_leap?(year)
-
COMMON_YEAR_DAYS_IN_MONTH[month]
-
end
-
-
# Returns a new Time if requested year can be accommodated by Ruby's Time class
-
# (i.e., if year is within either 1970..2038 or 1902..2038, depending on system architecture);
-
# otherwise returns a DateTime.
-
1
def time_with_datetime_fallback(utc_or_local, year, month=1, day=1, hour=0, min=0, sec=0, usec=0)
-
time = ::Time.send(utc_or_local, year, month, day, hour, min, sec, usec)
-
# This check is needed because Time.utc(y) returns a time object in the 2000s for 0 <= y <= 138.
-
time.year == year ? time : ::DateTime.civil_from_format(utc_or_local, year, month, day, hour, min, sec)
-
rescue
-
::DateTime.civil_from_format(utc_or_local, year, month, day, hour, min, sec)
-
end
-
-
# Wraps class method +time_with_datetime_fallback+ with +utc_or_local+ set to <tt>:utc</tt>.
-
1
def utc_time(*args)
-
time_with_datetime_fallback(:utc, *args)
-
end
-
-
# Wraps class method +time_with_datetime_fallback+ with +utc_or_local+ set to <tt>:local</tt>.
-
1
def local_time(*args)
-
time_with_datetime_fallback(:local, *args)
-
end
-
-
# Returns <tt>Time.zone.now</tt> when <tt>Time.zone</tt> or <tt>config.time_zone</tt> are set, otherwise just returns <tt>Time.now</tt>.
-
1
def current
-
::Time.zone ? ::Time.zone.now : ::Time.now
-
end
-
end
-
-
# Tells whether the Time object's time lies in the past
-
1
def past?
-
self < ::Time.current
-
end
-
-
# Tells whether the Time object's time is today
-
1
def today?
-
to_date == ::Date.current
-
end
-
-
# Tells whether the Time object's time lies in the future
-
1
def future?
-
self > ::Time.current
-
end
-
-
# Seconds since midnight: Time.now.seconds_since_midnight
-
1
def seconds_since_midnight
-
to_i - change(:hour => 0).to_i + (usec / 1.0e+6)
-
end
-
-
# Returns a new Time where one or more of the elements have been changed according to the +options+ parameter. The time options
-
# (hour, minute, sec, usec) reset cascadingly, so if only the hour is passed, then minute, sec, and usec is set to 0. If the hour and
-
# minute is passed, then sec and usec is set to 0.
-
1
def change(options)
-
::Time.send(
-
utc? ? :utc_time : :local_time,
-
options[:year] || year,
-
options[:month] || month,
-
options[:day] || day,
-
options[:hour] || hour,
-
options[:min] || (options[:hour] ? 0 : min),
-
options[:sec] || ((options[:hour] || options[:min]) ? 0 : sec),
-
options[:usec] || ((options[:hour] || options[:min] || options[:sec]) ? 0 : usec)
-
)
-
end
-
-
# Uses Date to provide precise Time calculations for years, months, and days.
-
# The +options+ parameter takes a hash with any of these keys: <tt>:years</tt>,
-
# <tt>:months</tt>, <tt>:weeks</tt>, <tt>:days</tt>, <tt>:hours</tt>,
-
# <tt>:minutes</tt>, <tt>:seconds</tt>.
-
1
def advance(options)
-
unless options[:weeks].nil?
-
options[:weeks], partial_weeks = options[:weeks].divmod(1)
-
options[:days] = (options[:days] || 0) + 7 * partial_weeks
-
end
-
-
unless options[:days].nil?
-
options[:days], partial_days = options[:days].divmod(1)
-
options[:hours] = (options[:hours] || 0) + 24 * partial_days
-
end
-
-
d = to_date.advance(options)
-
time_advanced_by_date = change(:year => d.year, :month => d.month, :day => d.day)
-
seconds_to_advance = (options[:seconds] || 0) + (options[:minutes] || 0) * 60 + (options[:hours] || 0) * 3600
-
seconds_to_advance == 0 ? time_advanced_by_date : time_advanced_by_date.since(seconds_to_advance)
-
end
-
-
# Returns a new Time representing the time a number of seconds ago, this is basically a wrapper around the Numeric extension
-
1
def ago(seconds)
-
since(-seconds)
-
end
-
-
# Returns a new Time representing the time a number of seconds since the instance time
-
1
def since(seconds)
-
self + seconds
-
rescue
-
to_datetime.since(seconds)
-
end
-
1
alias :in :since
-
-
# Returns a new Time representing the time a number of specified weeks ago.
-
1
def weeks_ago(weeks)
-
advance(:weeks => -weeks)
-
end
-
-
# Returns a new Time representing the time a number of specified months ago
-
1
def months_ago(months)
-
advance(:months => -months)
-
end
-
-
# Returns a new Time representing the time a number of specified months in the future
-
1
def months_since(months)
-
advance(:months => months)
-
end
-
-
# Returns a new Time representing the time a number of specified years ago
-
1
def years_ago(years)
-
advance(:years => -years)
-
end
-
-
# Returns a new Time representing the time a number of specified years in the future
-
1
def years_since(years)
-
advance(:years => years)
-
end
-
-
# Short-hand for years_ago(1)
-
1
def prev_year
-
years_ago(1)
-
end
-
-
# Short-hand for years_since(1)
-
1
def next_year
-
years_since(1)
-
end
-
-
# Short-hand for months_ago(1)
-
1
def prev_month
-
months_ago(1)
-
end
-
-
# Short-hand for months_since(1)
-
1
def next_month
-
months_since(1)
-
end
-
-
# Returns a new Time representing the "start" of this week (Monday, 0:00)
-
1
def beginning_of_week
-
days_to_monday = wday!=0 ? wday-1 : 6
-
(self - days_to_monday.days).midnight
-
end
-
1
alias :monday :beginning_of_week
-
1
alias :at_beginning_of_week :beginning_of_week
-
-
# Returns a new Time representing the end of this week, (end of Sunday)
-
1
def end_of_week
-
days_to_sunday = wday!=0 ? 7-wday : 0
-
(self + days_to_sunday.days).end_of_day
-
end
-
1
alias :at_end_of_week :end_of_week
-
-
# Returns a new Time representing the start of the given day in the previous week (default is Monday).
-
1
def prev_week(day = :monday)
-
ago(1.week).beginning_of_week.since(DAYS_INTO_WEEK[day].day).change(:hour => 0)
-
end
-
-
# Returns a new Time representing the start of the given day in next week (default is Monday).
-
1
def next_week(day = :monday)
-
since(1.week).beginning_of_week.since(DAYS_INTO_WEEK[day].day).change(:hour => 0)
-
end
-
-
# Returns a new Time representing the start of the day (0:00)
-
1
def beginning_of_day
-
#(self - seconds_since_midnight).change(:usec => 0)
-
change(:hour => 0)
-
end
-
1
alias :midnight :beginning_of_day
-
1
alias :at_midnight :beginning_of_day
-
1
alias :at_beginning_of_day :beginning_of_day
-
-
# Returns a new Time representing the end of the day, 23:59:59.999999 (.999999999 in ruby1.9)
-
1
def end_of_day
-
change(:hour => 23, :min => 59, :sec => 59, :usec => 999999.999)
-
end
-
-
# Returns a new Time representing the start of the month (1st of the month, 0:00)
-
1
def beginning_of_month
-
#self - ((self.mday-1).days + self.seconds_since_midnight)
-
change(:day => 1, :hour => 0)
-
end
-
1
alias :at_beginning_of_month :beginning_of_month
-
-
# Returns a new Time representing the end of the month (end of the last day of the month)
-
1
def end_of_month
-
#self - ((self.mday-1).days + self.seconds_since_midnight)
-
last_day = ::Time.days_in_month(month, year)
-
change(:day => last_day, :hour => 23, :min => 59, :sec => 59, :usec => 999999.999)
-
end
-
1
alias :at_end_of_month :end_of_month
-
-
# Returns a new Time representing the start of the quarter (1st of january, april, july, october, 0:00)
-
1
def beginning_of_quarter
-
beginning_of_month.change(:month => [10, 7, 4, 1].detect { |m| m <= month })
-
end
-
1
alias :at_beginning_of_quarter :beginning_of_quarter
-
-
# Returns a new Time representing the end of the quarter (end of the last day of march, june, september, december)
-
1
def end_of_quarter
-
beginning_of_month.change(:month => [3, 6, 9, 12].detect { |m| m >= month }).end_of_month
-
end
-
1
alias :at_end_of_quarter :end_of_quarter
-
-
# Returns a new Time representing the start of the year (1st of january, 0:00)
-
1
def beginning_of_year
-
change(:month => 1, :day => 1, :hour => 0)
-
end
-
1
alias :at_beginning_of_year :beginning_of_year
-
-
# Returns a new Time representing the end of the year (end of the 31st of december)
-
1
def end_of_year
-
change(:month => 12, :day => 31, :hour => 23, :min => 59, :sec => 59, :usec => 999999.999)
-
end
-
1
alias :at_end_of_year :end_of_year
-
-
# Convenience method which returns a new Time representing the time 1 day ago
-
1
def yesterday
-
advance(:days => -1)
-
end
-
-
# Convenience method which returns a new Time representing the time 1 day since the instance time
-
1
def tomorrow
-
advance(:days => 1)
-
end
-
-
1
def plus_with_duration(other) #:nodoc:
-
if ActiveSupport::Duration === other
-
other.since(self)
-
else
-
plus_without_duration(other)
-
end
-
end
-
1
alias_method :plus_without_duration, :+
-
1
alias_method :+, :plus_with_duration
-
-
1
def minus_with_duration(other) #:nodoc:
-
if ActiveSupport::Duration === other
-
other.until(self)
-
else
-
minus_without_duration(other)
-
end
-
end
-
1
alias_method :minus_without_duration, :-
-
1
alias_method :-, :minus_with_duration
-
-
# Time#- can also be used to determine the number of seconds between two Time instances.
-
# We're layering on additional behavior so that ActiveSupport::TimeWithZone instances
-
# are coerced into values that Time#- will recognize
-
1
def minus_with_coercion(other)
-
other = other.comparable_time if other.respond_to?(:comparable_time)
-
other.is_a?(DateTime) ? to_f - other.to_f : minus_without_coercion(other)
-
end
-
1
alias_method :minus_without_coercion, :-
-
1
alias_method :-, :minus_with_coercion
-
-
# Layers additional behavior on Time#<=> so that DateTime and ActiveSupport::TimeWithZone instances
-
# can be chronologically compared with a Time
-
1
def compare_with_coercion(other)
-
# we're avoiding Time#to_datetime cause it's expensive
-
other.is_a?(Time) ? compare_without_coercion(other.to_time) : to_datetime <=> other
-
end
-
1
alias_method :compare_without_coercion, :<=>
-
1
alias_method :<=>, :compare_with_coercion
-
end
-
1
require 'active_support/inflector/methods'
-
1
require 'active_support/core_ext/time/publicize_conversion_methods'
-
1
require 'active_support/values/time_zone'
-
-
1
class Time
-
1
DATE_FORMATS = {
-
:db => "%Y-%m-%d %H:%M:%S",
-
:number => "%Y%m%d%H%M%S",
-
:time => "%H:%M",
-
:short => "%d %b %H:%M",
-
:long => "%B %d, %Y %H:%M",
-
:long_ordinal => lambda { |time| time.strftime("%B #{ActiveSupport::Inflector.ordinalize(time.day)}, %Y %H:%M") },
-
:rfc822 => lambda { |time| time.strftime("%a, %d %b %Y %H:%M:%S #{time.formatted_offset(false)}") }
-
}
-
-
# Converts to a formatted string. See DATE_FORMATS for builtin formats.
-
#
-
# This method is aliased to <tt>to_s</tt>.
-
#
-
# time = Time.now # => Thu Jan 18 06:10:17 CST 2007
-
#
-
# time.to_formatted_s(:time) # => "06:10"
-
# time.to_s(:time) # => "06:10"
-
#
-
# time.to_formatted_s(:db) # => "2007-01-18 06:10:17"
-
# time.to_formatted_s(:number) # => "20070118061017"
-
# time.to_formatted_s(:short) # => "18 Jan 06:10"
-
# time.to_formatted_s(:long) # => "January 18, 2007 06:10"
-
# time.to_formatted_s(:long_ordinal) # => "January 18th, 2007 06:10"
-
# time.to_formatted_s(:rfc822) # => "Thu, 18 Jan 2007 06:10:17 -0600"
-
#
-
# == Adding your own time formats to +to_formatted_s+
-
# You can add your own formats to the Time::DATE_FORMATS hash.
-
# Use the format name as the hash key and either a strftime string
-
# or Proc instance that takes a time argument as the value.
-
#
-
# # config/initializers/time_formats.rb
-
# Time::DATE_FORMATS[:month_and_year] = "%B %Y"
-
# Time::DATE_FORMATS[:short_ordinal] = lambda { |time| time.strftime("%B #{time.day.ordinalize}") }
-
1
def to_formatted_s(format = :default)
-
if formatter = DATE_FORMATS[format]
-
formatter.respond_to?(:call) ? formatter.call(self).to_s : strftime(formatter)
-
else
-
to_default_s
-
end
-
end
-
1
alias_method :to_default_s, :to_s
-
1
alias_method :to_s, :to_formatted_s
-
-
# Returns the UTC offset as an +HH:MM formatted string.
-
#
-
# Time.local(2000).formatted_offset # => "-06:00"
-
# Time.local(2000).formatted_offset(false) # => "-0600"
-
1
def formatted_offset(colon = true, alternate_utc_string = nil)
-
utc? && alternate_utc_string || ActiveSupport::TimeZone.seconds_to_utc_offset(utc_offset, colon)
-
end
-
-
# A method to keep Time, Date and DateTime instances interchangeable on conversions.
-
# In this case, it simply returns +self+.
-
def to_time
-
self
-
1
end unless method_defined?(:to_time)
-
end
-
1
require 'date'
-
-
1
class Time
-
# Ruby 1.8-cvs and early 1.9 series define private Time#to_date
-
1
%w(to_date to_datetime).each do |method|
-
2
if private_instance_methods.include?(method) || private_instance_methods.include?(method.to_sym)
-
public method
-
end
-
end
-
end
-
1
require 'active_support/time_with_zone'
-
-
1
class Time
-
1
class << self
-
1
attr_accessor :zone_default
-
-
# Returns the TimeZone for the current request, if this has been set (via Time.zone=).
-
# If <tt>Time.zone</tt> has not been set for the current request, returns the TimeZone specified in <tt>config.time_zone</tt>.
-
1
def zone
-
Thread.current[:time_zone] || zone_default
-
end
-
-
# Sets <tt>Time.zone</tt> to a TimeZone object for the current request/thread.
-
#
-
# This method accepts any of the following:
-
#
-
# * A Rails TimeZone object.
-
# * An identifier for a Rails TimeZone object (e.g., "Eastern Time (US & Canada)", <tt>-5.hours</tt>).
-
# * A TZInfo::Timezone object.
-
# * An identifier for a TZInfo::Timezone object (e.g., "America/New_York").
-
#
-
# Here's an example of how you might set <tt>Time.zone</tt> on a per request basis and reset it when the request is done.
-
# <tt>current_user.time_zone</tt> just needs to return a string identifying the user's preferred time zone:
-
#
-
# class ApplicationController < ActionController::Base
-
# around_filter :set_time_zone
-
#
-
# def set_time_zone
-
# old_time_zone = Time.zone
-
# Time.zone = current_user.time_zone if logged_in?
-
# yield
-
# ensure
-
# Time.zone = old_time_zone
-
# end
-
# end
-
1
def zone=(time_zone)
-
Thread.current[:time_zone] = find_zone!(time_zone)
-
end
-
-
# Allows override of <tt>Time.zone</tt> locally inside supplied block; resets <tt>Time.zone</tt> to existing value when done.
-
1
def use_zone(time_zone)
-
new_zone = find_zone!(time_zone)
-
begin
-
old_zone, ::Time.zone = ::Time.zone, new_zone
-
yield
-
ensure
-
::Time.zone = old_zone
-
end
-
end
-
-
# Returns a TimeZone instance or nil, or raises an ArgumentError for invalid timezones.
-
1
def find_zone!(time_zone)
-
return time_zone if time_zone.nil? || time_zone.is_a?(ActiveSupport::TimeZone)
-
# lookup timezone based on identifier (unless we've been passed a TZInfo::Timezone)
-
unless time_zone.respond_to?(:period_for_local)
-
time_zone = ActiveSupport::TimeZone[time_zone] || TZInfo::Timezone.get(time_zone)
-
end
-
# Return if a TimeZone instance, or wrap in a TimeZone instance if a TZInfo::Timezone
-
time_zone.is_a?(ActiveSupport::TimeZone) ? time_zone : ActiveSupport::TimeZone.create(time_zone.name, nil, time_zone)
-
rescue TZInfo::InvalidTimezoneIdentifier
-
raise ArgumentError, "Invalid Timezone: #{time_zone}"
-
end
-
-
1
def find_zone(time_zone)
-
find_zone!(time_zone) rescue nil
-
end
-
end
-
-
# Returns the simultaneous time in <tt>Time.zone</tt>.
-
#
-
# Time.zone = 'Hawaii' # => 'Hawaii'
-
# Time.utc(2000).in_time_zone # => Fri, 31 Dec 1999 14:00:00 HST -10:00
-
#
-
# This method is similar to Time#localtime, except that it uses <tt>Time.zone</tt> as the local zone
-
# instead of the operating system's time zone.
-
#
-
# You can also pass in a TimeZone instance or string that identifies a TimeZone as an argument,
-
# and the conversion will be based on that zone instead of <tt>Time.zone</tt>.
-
#
-
# Time.utc(2000).in_time_zone('Alaska') # => Fri, 31 Dec 1999 15:00:00 AKST -09:00
-
1
def in_time_zone(zone = ::Time.zone)
-
return self unless zone
-
-
ActiveSupport::TimeWithZone.new(utc? ? self : getutc, ::Time.find_zone!(zone))
-
end
-
end
-
# encoding: utf-8
-
-
1
if RUBY_VERSION >= '1.9'
-
1
require 'uri'
-
-
1
str = "\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E" # Ni-ho-nn-go in UTF-8, means Japanese.
-
-
1
parser = URI::Parser.new
-
-
1
unless str == parser.unescape(parser.escape(str))
-
1
URI::Parser.class_eval do
-
1
remove_method :unescape
-
1
def unescape(str, escaped = /%[a-fA-F\d]{2}/)
-
# TODO: Are we actually sure that ASCII == UTF-8?
-
# YK: My initial experiments say yes, but let's be sure please
-
enc = str.encoding
-
enc = Encoding::UTF_8 if enc == Encoding::US_ASCII
-
str.gsub(escaped) { [$&[1, 2].hex].pack('C') }.force_encoding(enc)
-
end
-
end
-
end
-
end
-
-
1
module URI
-
1
class << self
-
1
def parser
-
@parser ||= URI.const_defined?(:Parser) ? URI::Parser.new : URI
-
end
-
end
-
end
-
1
require 'active_support/basic_object'
-
1
require 'active_support/core_ext/array/conversions'
-
1
require 'active_support/core_ext/object/acts_like'
-
-
1
module ActiveSupport
-
# Provides accurate date and time measurements using Date#advance and
-
# Time#advance, respectively. It mainly supports the methods on Numeric.
-
# Example:
-
#
-
# 1.month.ago # equivalent to Time.now.advance(:months => -1)
-
1
class Duration < BasicObject
-
1
attr_accessor :value, :parts
-
1
delegate :duplicable?, :to => :value # required when using ActiveSupport's BasicObject on 1.8
-
-
1
def initialize(value, parts) #:nodoc:
-
@value, @parts = value, parts
-
end
-
-
# Adds another Duration or a Numeric to this Duration. Numeric values
-
# are treated as seconds.
-
1
def +(other)
-
if Duration === other
-
Duration.new(value + other.value, @parts + other.parts)
-
else
-
Duration.new(value + other, @parts + [[:seconds, other]])
-
end
-
end
-
-
# Subtracts another Duration or a Numeric from this Duration. Numeric
-
# values are treated as seconds.
-
1
def -(other)
-
self + (-other)
-
end
-
-
1
def -@ #:nodoc:
-
Duration.new(-value, parts.map { |type,number| [type, -number] })
-
end
-
-
1
def is_a?(klass) #:nodoc:
-
Duration == klass || value.is_a?(klass)
-
end
-
1
alias :kind_of? :is_a?
-
-
# Returns true if <tt>other</tt> is also a Duration instance with the
-
# same <tt>value</tt>, or if <tt>other == value</tt>.
-
1
def ==(other)
-
if Duration === other
-
other.value == value
-
else
-
other == value
-
end
-
end
-
-
1
def self.===(other) #:nodoc:
-
other.is_a?(Duration)
-
rescue ::NoMethodError
-
false
-
end
-
-
# Calculates a new Time or Date that is as far in the future
-
# as this Duration represents.
-
1
def since(time = ::Time.current)
-
sum(1, time)
-
end
-
1
alias :from_now :since
-
-
# Calculates a new Time or Date that is as far in the past
-
# as this Duration represents.
-
1
def ago(time = ::Time.current)
-
sum(-1, time)
-
end
-
1
alias :until :ago
-
-
1
def inspect #:nodoc:
-
consolidated = parts.inject(::Hash.new(0)) { |h,part| h[part.first] += part.last; h }
-
parts = [:years, :months, :days, :minutes, :seconds].map do |length|
-
n = consolidated[length]
-
"#{n} #{n == 1 ? length.to_s.singularize : length.to_s}" if n.nonzero?
-
end.compact
-
parts = ["0 seconds"] if parts.empty?
-
parts.to_sentence(:locale => :en)
-
end
-
-
1
def as_json(options = nil) #:nodoc:
-
to_i
-
end
-
-
1
protected
-
-
1
def sum(sign, time = ::Time.current) #:nodoc:
-
parts.inject(time) do |t,(type,number)|
-
if t.acts_like?(:time) || t.acts_like?(:date)
-
if type == :seconds
-
t.since(sign * number)
-
else
-
t.advance(type => sign * number)
-
end
-
else
-
raise ::ArgumentError, "expected a time or date, got #{time.inspect}"
-
end
-
end
-
end
-
-
1
private
-
-
1
def method_missing(method, *args, &block) #:nodoc:
-
value.send(method, *args, &block)
-
end
-
end
-
end
-
# Backported Ruby builtins so you can code with the latest & greatest
-
# but still run on any Ruby 1.8.x.
-
#
-
# Date next_year, next_month
-
# DateTime to_date, to_datetime, xmlschema
-
# Enumerable group_by, each_with_object, none?
-
# Process Process.daemon
-
# REXML security fix
-
# String ord
-
# Time to_date, to_time, to_datetime
-
1
require 'active_support'
-
1
require 'active_support/core_ext/date/calculations'
-
1
require 'active_support/core_ext/date_time/conversions'
-
1
require 'active_support/core_ext/enumerable'
-
1
require 'active_support/core_ext/process/daemon'
-
1
require 'active_support/core_ext/string/conversions'
-
1
require 'active_support/core_ext/string/interpolation'
-
1
require 'active_support/core_ext/string/encoding'
-
1
require 'active_support/core_ext/rexml'
-
1
require 'active_support/core_ext/time/conversions'
-
1
require 'active_support/core_ext/file/path'
-
1
require 'active_support/core_ext/module/method_names'
-
1
module ActiveSupport
-
# Wrapping a string in this class gives you a prettier way to test
-
# for equality. The value returned by <tt>Rails.env</tt> is wrapped
-
# in a StringInquirer object so instead of calling this:
-
#
-
# Rails.env == "production"
-
#
-
# you can call this:
-
#
-
# Rails.env.production?
-
#
-
1
class StringInquirer < String
-
1
def method_missing(method_name, *arguments)
-
if method_name.to_s[-1,1] == "?"
-
self == method_name.to_s[0..-2]
-
else
-
super
-
end
-
end
-
end
-
end
-
1
require "active_support/values/time_zone"
-
1
require 'active_support/core_ext/object/acts_like'
-
1
require 'active_support/core_ext/object/inclusion'
-
-
1
module ActiveSupport
-
# A Time-like class that can represent a time in any time zone. Necessary because standard Ruby Time instances are
-
# limited to UTC and the system's <tt>ENV['TZ']</tt> zone.
-
#
-
# You shouldn't ever need to create a TimeWithZone instance directly via <tt>new</tt> . Instead use methods
-
# +local+, +parse+, +at+ and +now+ on TimeZone instances, and +in_time_zone+ on Time and DateTime instances.
-
# Examples:
-
#
-
# Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)'
-
# Time.zone.local(2007, 2, 10, 15, 30, 45) # => Sat, 10 Feb 2007 15:30:45 EST -05:00
-
# Time.zone.parse('2007-02-01 15:30:45') # => Sat, 10 Feb 2007 15:30:45 EST -05:00
-
# Time.zone.at(1170361845) # => Sat, 10 Feb 2007 15:30:45 EST -05:00
-
# Time.zone.now # => Sun, 18 May 2008 13:07:55 EDT -04:00
-
# Time.utc(2007, 2, 10, 20, 30, 45).in_time_zone # => Sat, 10 Feb 2007 15:30:45 EST -05:00
-
#
-
# See Time and TimeZone for further documentation of these methods.
-
#
-
# TimeWithZone instances implement the same API as Ruby Time instances, so that Time and TimeWithZone instances are interchangeable.
-
# Examples:
-
#
-
# t = Time.zone.now # => Sun, 18 May 2008 13:27:25 EDT -04:00
-
# t.hour # => 13
-
# t.dst? # => true
-
# t.utc_offset # => -14400
-
# t.zone # => "EDT"
-
# t.to_s(:rfc822) # => "Sun, 18 May 2008 13:27:25 -0400"
-
# t + 1.day # => Mon, 19 May 2008 13:27:25 EDT -04:00
-
# t.beginning_of_year # => Tue, 01 Jan 2008 00:00:00 EST -05:00
-
# t > Time.utc(1999) # => true
-
# t.is_a?(Time) # => true
-
# t.is_a?(ActiveSupport::TimeWithZone) # => true
-
#
-
1
class TimeWithZone
-
1
def self.name
-
'Time' # Report class name as 'Time' to thwart type checking
-
end
-
-
1
include Comparable
-
1
attr_reader :time_zone
-
-
1
def initialize(utc_time, time_zone, local_time = nil, period = nil)
-
@utc, @time_zone, @time = utc_time, time_zone, local_time
-
@period = @utc ? period : get_period_and_ensure_valid_local_time
-
end
-
-
# Returns a Time or DateTime instance that represents the time in +time_zone+.
-
1
def time
-
@time ||= period.to_local(@utc)
-
end
-
-
# Returns a Time or DateTime instance that represents the time in UTC.
-
1
def utc
-
@utc ||= period.to_utc(@time)
-
end
-
1
alias_method :comparable_time, :utc
-
1
alias_method :getgm, :utc
-
1
alias_method :getutc, :utc
-
1
alias_method :gmtime, :utc
-
-
# Returns the underlying TZInfo::TimezonePeriod.
-
1
def period
-
@period ||= time_zone.period_for_utc(@utc)
-
end
-
-
# Returns the simultaneous time in <tt>Time.zone</tt>, or the specified zone.
-
1
def in_time_zone(new_zone = ::Time.zone)
-
return self if time_zone == new_zone
-
utc.in_time_zone(new_zone)
-
end
-
-
# Returns a <tt>Time.local()</tt> instance of the simultaneous time in your system's <tt>ENV['TZ']</tt> zone
-
1
def localtime
-
utc.respond_to?(:getlocal) ? utc.getlocal : utc.to_time.getlocal
-
end
-
1
alias_method :getlocal, :localtime
-
-
1
def dst?
-
period.dst?
-
end
-
1
alias_method :isdst, :dst?
-
-
1
def utc?
-
time_zone.name == 'UTC'
-
end
-
1
alias_method :gmt?, :utc?
-
-
1
def utc_offset
-
period.utc_total_offset
-
end
-
1
alias_method :gmt_offset, :utc_offset
-
1
alias_method :gmtoff, :utc_offset
-
-
1
def formatted_offset(colon = true, alternate_utc_string = nil)
-
utc? && alternate_utc_string || TimeZone.seconds_to_utc_offset(utc_offset, colon)
-
end
-
-
# Time uses +zone+ to display the time zone abbreviation, so we're duck-typing it.
-
1
def zone
-
period.zone_identifier.to_s
-
end
-
-
1
def inspect
-
"#{time.strftime('%a, %d %b %Y %H:%M:%S')} #{zone} #{formatted_offset}"
-
end
-
-
1
def xmlschema(fraction_digits = 0)
-
fraction = if fraction_digits > 0
-
".%i" % time.usec.to_s[0, fraction_digits]
-
end
-
-
"#{time.strftime("%Y-%m-%dT%H:%M:%S")}#{fraction}#{formatted_offset(true, 'Z')}"
-
end
-
1
alias_method :iso8601, :xmlschema
-
-
# Coerces time to a string for JSON encoding. The default format is ISO 8601. You can get
-
# %Y/%m/%d %H:%M:%S +offset style by setting <tt>ActiveSupport::JSON::Encoding.use_standard_json_time_format</tt>
-
# to false.
-
#
-
# ==== Examples
-
#
-
# # With ActiveSupport::JSON::Encoding.use_standard_json_time_format = true
-
# Time.utc(2005,2,1,15,15,10).in_time_zone.to_json
-
# # => "2005-02-01T15:15:10Z"
-
#
-
# # With ActiveSupport::JSON::Encoding.use_standard_json_time_format = false
-
# Time.utc(2005,2,1,15,15,10).in_time_zone.to_json
-
# # => "2005/02/01 15:15:10 +0000"
-
#
-
1
def as_json(options = nil)
-
if ActiveSupport::JSON::Encoding.use_standard_json_time_format
-
xmlschema
-
else
-
%(#{time.strftime("%Y/%m/%d %H:%M:%S")} #{formatted_offset(false)})
-
end
-
end
-
-
1
def encode_with(coder)
-
if coder.respond_to?(:represent_object)
-
coder.represent_object(nil, utc)
-
else
-
coder.represent_scalar(nil, utc.strftime("%Y-%m-%d %H:%M:%S.%9NZ"))
-
end
-
end
-
-
1
def to_yaml(options = {})
-
return super if defined?(YAML::ENGINE) && !YAML::ENGINE.syck?
-
-
utc.to_yaml(options)
-
end
-
-
1
def httpdate
-
utc.httpdate
-
end
-
-
1
def rfc2822
-
to_s(:rfc822)
-
end
-
1
alias_method :rfc822, :rfc2822
-
-
# <tt>:db</tt> format outputs time in UTC; all others output time in local.
-
# Uses TimeWithZone's +strftime+, so <tt>%Z</tt> and <tt>%z</tt> work correctly.
-
1
def to_s(format = :default)
-
if format == :db
-
utc.to_s(format)
-
elsif formatter = ::Time::DATE_FORMATS[format]
-
formatter.respond_to?(:call) ? formatter.call(self).to_s : strftime(formatter)
-
else
-
"#{time.strftime("%Y-%m-%d %H:%M:%S")} #{formatted_offset(false, 'UTC')}" # mimicking Ruby 1.9 Time#to_s format
-
end
-
end
-
1
alias_method :to_formatted_s, :to_s
-
-
# Replaces <tt>%Z</tt> and <tt>%z</tt> directives with +zone+ and +formatted_offset+, respectively, before passing to
-
# Time#strftime, so that zone information is correct
-
1
def strftime(format)
-
format = format.gsub('%Z', zone).gsub('%z', formatted_offset(false))
-
time.strftime(format)
-
end
-
-
# Use the time in UTC for comparisons.
-
1
def <=>(other)
-
utc <=> other
-
end
-
-
1
def between?(min, max)
-
utc.between?(min, max)
-
end
-
-
1
def past?
-
utc.past?
-
end
-
-
1
def today?
-
time.today?
-
end
-
-
1
def future?
-
utc.future?
-
end
-
-
1
def eql?(other)
-
utc == other
-
end
-
-
1
def +(other)
-
# If we're adding a Duration of variable length (i.e., years, months, days), move forward from #time,
-
# otherwise move forward from #utc, for accuracy when moving across DST boundaries
-
if duration_of_variable_length?(other)
-
method_missing(:+, other)
-
else
-
result = utc.acts_like?(:date) ? utc.since(other) : utc + other rescue utc.since(other)
-
result.in_time_zone(time_zone)
-
end
-
end
-
-
1
def -(other)
-
# If we're subtracting a Duration of variable length (i.e., years, months, days), move backwards from #time,
-
# otherwise move backwards #utc, for accuracy when moving across DST boundaries
-
if other.acts_like?(:time)
-
utc.to_f - other.to_f
-
elsif duration_of_variable_length?(other)
-
method_missing(:-, other)
-
else
-
result = utc.acts_like?(:date) ? utc.ago(other) : utc - other rescue utc.ago(other)
-
result.in_time_zone(time_zone)
-
end
-
end
-
-
1
def since(other)
-
# If we're adding a Duration of variable length (i.e., years, months, days), move forward from #time,
-
# otherwise move forward from #utc, for accuracy when moving across DST boundaries
-
if duration_of_variable_length?(other)
-
method_missing(:since, other)
-
else
-
utc.since(other).in_time_zone(time_zone)
-
end
-
end
-
-
1
def ago(other)
-
since(-other)
-
end
-
-
1
def advance(options)
-
# If we're advancing a value of variable length (i.e., years, weeks, months, days), advance from #time,
-
# otherwise advance from #utc, for accuracy when moving across DST boundaries
-
if options.values_at(:years, :weeks, :months, :days).any?
-
method_missing(:advance, options)
-
else
-
utc.advance(options).in_time_zone(time_zone)
-
end
-
end
-
-
1
%w(year mon month day mday wday yday hour min sec to_date).each do |method_name|
-
11
class_eval <<-EOV, __FILE__, __LINE__ + 1
-
def #{method_name} # def month
-
time.#{method_name} # time.month
-
end # end
-
EOV
-
end
-
-
1
def usec
-
time.respond_to?(:usec) ? time.usec : 0
-
end
-
-
1
def to_a
-
[time.sec, time.min, time.hour, time.day, time.mon, time.year, time.wday, time.yday, dst?, zone]
-
end
-
-
1
def to_f
-
utc.to_f
-
end
-
-
1
def to_i
-
utc.to_i
-
end
-
1
alias_method :hash, :to_i
-
1
alias_method :tv_sec, :to_i
-
-
# A TimeWithZone acts like a Time, so just return +self+.
-
1
def to_time
-
utc
-
end
-
-
1
def to_datetime
-
utc.to_datetime.new_offset(Rational(utc_offset, 86_400))
-
end
-
-
# So that +self+ <tt>acts_like?(:time)</tt>.
-
1
def acts_like_time?
-
true
-
end
-
-
# Say we're a Time to thwart type checking.
-
1
def is_a?(klass)
-
klass == ::Time || super
-
end
-
1
alias_method :kind_of?, :is_a?
-
-
1
def freeze
-
period; utc; time # preload instance variables before freezing
-
super
-
end
-
-
1
def marshal_dump
-
[utc, time_zone.name, time]
-
end
-
-
1
def marshal_load(variables)
-
initialize(variables[0].utc, ::Time.find_zone(variables[1]), variables[2].utc)
-
end
-
-
# Ensure proxy class responds to all methods that underlying time instance responds to.
-
1
def respond_to?(sym, include_priv = false)
-
# consistently respond false to acts_like?(:date), regardless of whether #time is a Time or DateTime
-
return false if sym.to_s == 'acts_like_date?'
-
super || time.respond_to?(sym, include_priv)
-
end
-
-
# Send the missing method to +time+ instance, and wrap result in a new TimeWithZone with the existing +time_zone+.
-
1
def method_missing(sym, *args, &block)
-
result = time.__send__(sym, *args, &block)
-
result.acts_like?(:time) ? self.class.new(nil, time_zone, result) : result
-
end
-
-
1
private
-
1
def get_period_and_ensure_valid_local_time
-
# we don't want a Time.local instance enforcing its own DST rules as well,
-
# so transfer time values to a utc constructor if necessary
-
@time = transfer_time_values_to_utc_constructor(@time) unless @time.utc?
-
begin
-
@time_zone.period_for_local(@time)
-
rescue ::TZInfo::PeriodNotFound
-
# time is in the "spring forward" hour gap, so we're moving the time forward one hour and trying again
-
@time += 1.hour
-
retry
-
end
-
end
-
-
1
def transfer_time_values_to_utc_constructor(time)
-
::Time.utc_time(time.year, time.month, time.day, time.hour, time.min, time.sec, time.respond_to?(:usec) ? time.usec : 0)
-
end
-
-
1
def duration_of_variable_length?(obj)
-
ActiveSupport::Duration === obj && obj.parts.any? {|p| p[0].in?([:years, :months, :days]) }
-
end
-
end
-
end
-
1
require 'active_support/core_ext/object/blank'
-
1
require 'active_support/core_ext/object/try'
-
-
# The TimeZone class serves as a wrapper around TZInfo::Timezone instances. It allows us to do the following:
-
#
-
# * Limit the set of zones provided by TZInfo to a meaningful subset of 142 zones.
-
# * Retrieve and display zones with a friendlier name (e.g., "Eastern Time (US & Canada)" instead of "America/New_York").
-
# * Lazily load TZInfo::Timezone instances only when they're needed.
-
# * Create ActiveSupport::TimeWithZone instances via TimeZone's +local+, +parse+, +at+ and +now+ methods.
-
#
-
# If you set <tt>config.time_zone</tt> in the Rails Application, you can access this TimeZone object via <tt>Time.zone</tt>:
-
#
-
# # application.rb:
-
# class Application < Rails::Application
-
# config.time_zone = "Eastern Time (US & Canada)"
-
# end
-
#
-
# Time.zone # => #<TimeZone:0x514834...>
-
# Time.zone.name # => "Eastern Time (US & Canada)"
-
# Time.zone.now # => Sun, 18 May 2008 14:30:44 EDT -04:00
-
#
-
# The version of TZInfo bundled with Active Support only includes the definitions necessary to support the zones
-
# defined by the TimeZone class. If you need to use zones that aren't defined by TimeZone, you'll need to install the TZInfo gem
-
# (if a recent version of the gem is installed locally, this will be used instead of the bundled version.)
-
1
module ActiveSupport
-
1
class TimeZone
-
# Keys are Rails TimeZone names, values are TZInfo identifiers
-
1
MAPPING = {
-
"International Date Line West" => "Pacific/Midway",
-
"Midway Island" => "Pacific/Midway",
-
"Samoa" => "Pacific/Pago_Pago",
-
"Hawaii" => "Pacific/Honolulu",
-
"Alaska" => "America/Juneau",
-
"Pacific Time (US & Canada)" => "America/Los_Angeles",
-
"Tijuana" => "America/Tijuana",
-
"Mountain Time (US & Canada)" => "America/Denver",
-
"Arizona" => "America/Phoenix",
-
"Chihuahua" => "America/Chihuahua",
-
"Mazatlan" => "America/Mazatlan",
-
"Central Time (US & Canada)" => "America/Chicago",
-
"Saskatchewan" => "America/Regina",
-
"Guadalajara" => "America/Mexico_City",
-
"Mexico City" => "America/Mexico_City",
-
"Monterrey" => "America/Monterrey",
-
"Central America" => "America/Guatemala",
-
"Eastern Time (US & Canada)" => "America/New_York",
-
"Indiana (East)" => "America/Indiana/Indianapolis",
-
"Bogota" => "America/Bogota",
-
"Lima" => "America/Lima",
-
"Quito" => "America/Lima",
-
"Atlantic Time (Canada)" => "America/Halifax",
-
"Caracas" => "America/Caracas",
-
"La Paz" => "America/La_Paz",
-
"Santiago" => "America/Santiago",
-
"Newfoundland" => "America/St_Johns",
-
"Brasilia" => "America/Sao_Paulo",
-
"Buenos Aires" => "America/Argentina/Buenos_Aires",
-
"Georgetown" => "America/Guyana",
-
"Greenland" => "America/Godthab",
-
"Mid-Atlantic" => "Atlantic/South_Georgia",
-
"Azores" => "Atlantic/Azores",
-
"Cape Verde Is." => "Atlantic/Cape_Verde",
-
"Dublin" => "Europe/Dublin",
-
"Edinburgh" => "Europe/London",
-
"Lisbon" => "Europe/Lisbon",
-
"London" => "Europe/London",
-
"Casablanca" => "Africa/Casablanca",
-
"Monrovia" => "Africa/Monrovia",
-
"UTC" => "Etc/UTC",
-
"Belgrade" => "Europe/Belgrade",
-
"Bratislava" => "Europe/Bratislava",
-
"Budapest" => "Europe/Budapest",
-
"Ljubljana" => "Europe/Ljubljana",
-
"Prague" => "Europe/Prague",
-
"Sarajevo" => "Europe/Sarajevo",
-
"Skopje" => "Europe/Skopje",
-
"Warsaw" => "Europe/Warsaw",
-
"Zagreb" => "Europe/Zagreb",
-
"Brussels" => "Europe/Brussels",
-
"Copenhagen" => "Europe/Copenhagen",
-
"Madrid" => "Europe/Madrid",
-
"Paris" => "Europe/Paris",
-
"Amsterdam" => "Europe/Amsterdam",
-
"Berlin" => "Europe/Berlin",
-
"Bern" => "Europe/Berlin",
-
"Rome" => "Europe/Rome",
-
"Stockholm" => "Europe/Stockholm",
-
"Vienna" => "Europe/Vienna",
-
"West Central Africa" => "Africa/Algiers",
-
"Bucharest" => "Europe/Bucharest",
-
"Cairo" => "Africa/Cairo",
-
"Helsinki" => "Europe/Helsinki",
-
"Kyiv" => "Europe/Kiev",
-
"Riga" => "Europe/Riga",
-
"Sofia" => "Europe/Sofia",
-
"Tallinn" => "Europe/Tallinn",
-
"Vilnius" => "Europe/Vilnius",
-
"Athens" => "Europe/Athens",
-
"Istanbul" => "Europe/Istanbul",
-
"Minsk" => "Europe/Minsk",
-
"Jerusalem" => "Asia/Jerusalem",
-
"Harare" => "Africa/Harare",
-
"Pretoria" => "Africa/Johannesburg",
-
"Moscow" => "Europe/Moscow",
-
"St. Petersburg" => "Europe/Moscow",
-
"Volgograd" => "Europe/Moscow",
-
"Kuwait" => "Asia/Kuwait",
-
"Riyadh" => "Asia/Riyadh",
-
"Nairobi" => "Africa/Nairobi",
-
"Baghdad" => "Asia/Baghdad",
-
"Tehran" => "Asia/Tehran",
-
"Abu Dhabi" => "Asia/Muscat",
-
"Muscat" => "Asia/Muscat",
-
"Baku" => "Asia/Baku",
-
"Tbilisi" => "Asia/Tbilisi",
-
"Yerevan" => "Asia/Yerevan",
-
"Kabul" => "Asia/Kabul",
-
"Ekaterinburg" => "Asia/Yekaterinburg",
-
"Islamabad" => "Asia/Karachi",
-
"Karachi" => "Asia/Karachi",
-
"Tashkent" => "Asia/Tashkent",
-
"Chennai" => "Asia/Kolkata",
-
"Kolkata" => "Asia/Kolkata",
-
"Mumbai" => "Asia/Kolkata",
-
"New Delhi" => "Asia/Kolkata",
-
"Kathmandu" => "Asia/Kathmandu",
-
"Astana" => "Asia/Dhaka",
-
"Dhaka" => "Asia/Dhaka",
-
"Sri Jayawardenepura" => "Asia/Colombo",
-
"Almaty" => "Asia/Almaty",
-
"Novosibirsk" => "Asia/Novosibirsk",
-
"Rangoon" => "Asia/Rangoon",
-
"Bangkok" => "Asia/Bangkok",
-
"Hanoi" => "Asia/Bangkok",
-
"Jakarta" => "Asia/Jakarta",
-
"Krasnoyarsk" => "Asia/Krasnoyarsk",
-
"Beijing" => "Asia/Shanghai",
-
"Chongqing" => "Asia/Chongqing",
-
"Hong Kong" => "Asia/Hong_Kong",
-
"Urumqi" => "Asia/Urumqi",
-
"Kuala Lumpur" => "Asia/Kuala_Lumpur",
-
"Singapore" => "Asia/Singapore",
-
"Taipei" => "Asia/Taipei",
-
"Perth" => "Australia/Perth",
-
"Irkutsk" => "Asia/Irkutsk",
-
"Ulaan Bataar" => "Asia/Ulaanbaatar",
-
"Seoul" => "Asia/Seoul",
-
"Osaka" => "Asia/Tokyo",
-
"Sapporo" => "Asia/Tokyo",
-
"Tokyo" => "Asia/Tokyo",
-
"Yakutsk" => "Asia/Yakutsk",
-
"Darwin" => "Australia/Darwin",
-
"Adelaide" => "Australia/Adelaide",
-
"Canberra" => "Australia/Melbourne",
-
"Melbourne" => "Australia/Melbourne",
-
"Sydney" => "Australia/Sydney",
-
"Brisbane" => "Australia/Brisbane",
-
"Hobart" => "Australia/Hobart",
-
"Vladivostok" => "Asia/Vladivostok",
-
"Guam" => "Pacific/Guam",
-
"Port Moresby" => "Pacific/Port_Moresby",
-
"Magadan" => "Asia/Magadan",
-
"Solomon Is." => "Asia/Magadan",
-
"New Caledonia" => "Pacific/Noumea",
-
"Fiji" => "Pacific/Fiji",
-
"Kamchatka" => "Asia/Kamchatka",
-
"Marshall Is." => "Pacific/Majuro",
-
"Auckland" => "Pacific/Auckland",
-
"Wellington" => "Pacific/Auckland",
-
"Nuku'alofa" => "Pacific/Tongatapu"
-
142
}.each { |name, zone| name.freeze; zone.freeze }
-
1
MAPPING.freeze
-
-
1
UTC_OFFSET_WITH_COLON = '%s%02d:%02d'
-
1
UTC_OFFSET_WITHOUT_COLON = UTC_OFFSET_WITH_COLON.sub(':', '')
-
-
# Assumes self represents an offset from UTC in seconds (as returned from Time#utc_offset)
-
# and turns this into an +HH:MM formatted string. Example:
-
#
-
# TimeZone.seconds_to_utc_offset(-21_600) # => "-06:00"
-
1
def self.seconds_to_utc_offset(seconds, colon = true)
-
format = colon ? UTC_OFFSET_WITH_COLON : UTC_OFFSET_WITHOUT_COLON
-
sign = (seconds < 0 ? '-' : '+')
-
hours = seconds.abs / 3600
-
minutes = (seconds.abs % 3600) / 60
-
format % [sign, hours, minutes]
-
end
-
-
1
include Comparable
-
1
attr_reader :name
-
1
attr_reader :tzinfo
-
-
# Create a new TimeZone object with the given name and offset. The
-
# offset is the number of seconds that this time zone is offset from UTC
-
# (GMT). Seconds were chosen as the offset unit because that is the unit that
-
# Ruby uses to represent time zone offsets (see Time#utc_offset).
-
1
def initialize(name, utc_offset = nil, tzinfo = nil)
-
self.class.send(:require_tzinfo)
-
-
@name = name
-
@utc_offset = utc_offset
-
@tzinfo = tzinfo || TimeZone.find_tzinfo(name)
-
@current_period = nil
-
end
-
-
1
def utc_offset
-
if @utc_offset
-
@utc_offset
-
else
-
@current_period ||= tzinfo.try(:current_period)
-
@current_period.try(:utc_offset)
-
end
-
end
-
-
# Returns the offset of this time zone as a formatted string, of the
-
# format "+HH:MM".
-
1
def formatted_offset(colon=true, alternate_utc_string = nil)
-
utc_offset == 0 && alternate_utc_string || self.class.seconds_to_utc_offset(utc_offset, colon)
-
end
-
-
# Compare this time zone to the parameter. The two are compared first on
-
# their offsets, and then by name.
-
1
def <=>(zone)
-
result = (utc_offset <=> zone.utc_offset)
-
result = (name <=> zone.name) if result == 0
-
result
-
end
-
-
# Compare #name and TZInfo identifier to a supplied regexp, returning true
-
# if a match is found.
-
1
def =~(re)
-
return true if name =~ re || MAPPING[name] =~ re
-
end
-
-
# Returns a textual representation of this time zone.
-
1
def to_s
-
"(GMT#{formatted_offset}) #{name}"
-
end
-
-
# Method for creating new ActiveSupport::TimeWithZone instance in time zone of +self+ from given values. Example:
-
#
-
# Time.zone = "Hawaii" # => "Hawaii"
-
# Time.zone.local(2007, 2, 1, 15, 30, 45) # => Thu, 01 Feb 2007 15:30:45 HST -10:00
-
1
def local(*args)
-
time = Time.utc_time(*args)
-
ActiveSupport::TimeWithZone.new(nil, self, time)
-
end
-
-
# Method for creating new ActiveSupport::TimeWithZone instance in time zone of +self+ from number of seconds since the Unix epoch. Example:
-
#
-
# Time.zone = "Hawaii" # => "Hawaii"
-
# Time.utc(2000).to_f # => 946684800.0
-
# Time.zone.at(946684800.0) # => Fri, 31 Dec 1999 14:00:00 HST -10:00
-
1
def at(secs)
-
utc = Time.at(secs).utc rescue DateTime.civil(1970).since(secs)
-
utc.in_time_zone(self)
-
end
-
-
# Method for creating new ActiveSupport::TimeWithZone instance in time zone of +self+ from parsed string. Example:
-
#
-
# Time.zone = "Hawaii" # => "Hawaii"
-
# Time.zone.parse('1999-12-31 14:00:00') # => Fri, 31 Dec 1999 14:00:00 HST -10:00
-
#
-
# If upper components are missing from the string, they are supplied from TimeZone#now:
-
#
-
# Time.zone.now # => Fri, 31 Dec 1999 14:00:00 HST -10:00
-
# Time.zone.parse('22:30:00') # => Fri, 31 Dec 1999 22:30:00 HST -10:00
-
1
def parse(str, now=now)
-
date_parts = Date._parse(str)
-
return if date_parts.blank?
-
time = Time.parse(str, now) rescue DateTime.parse(str)
-
if date_parts[:offset].nil?
-
ActiveSupport::TimeWithZone.new(nil, self, time)
-
else
-
time.in_time_zone(self)
-
end
-
end
-
-
# Returns an ActiveSupport::TimeWithZone instance representing the current time
-
# in the time zone represented by +self+. Example:
-
#
-
# Time.zone = 'Hawaii' # => "Hawaii"
-
# Time.zone.now # => Wed, 23 Jan 2008 20:24:27 HST -10:00
-
1
def now
-
Time.now.utc.in_time_zone(self)
-
end
-
-
# Return the current date in this time zone.
-
1
def today
-
tzinfo.now.to_date
-
end
-
-
# Adjust the given time to the simultaneous time in the time zone represented by +self+. Returns a
-
# Time.utc() instance -- if you want an ActiveSupport::TimeWithZone instance, use Time#in_time_zone() instead.
-
1
def utc_to_local(time)
-
tzinfo.utc_to_local(time)
-
end
-
-
# Adjust the given time to the simultaneous time in UTC. Returns a Time.utc() instance.
-
1
def local_to_utc(time, dst=true)
-
tzinfo.local_to_utc(time, dst)
-
end
-
-
# Available so that TimeZone instances respond like TZInfo::Timezone instances
-
1
def period_for_utc(time)
-
tzinfo.period_for_utc(time)
-
end
-
-
# Available so that TimeZone instances respond like TZInfo::Timezone instances
-
1
def period_for_local(time, dst=true)
-
tzinfo.period_for_local(time, dst)
-
end
-
-
# TODO: Preload instead of lazy load for thread safety
-
1
def self.find_tzinfo(name)
-
require 'active_support/tzinfo' unless defined?(::TZInfo)
-
::TZInfo::TimezoneProxy.new(MAPPING[name] || name)
-
end
-
-
1
class << self
-
1
alias_method :create, :new
-
-
# Return a TimeZone instance with the given name, or +nil+ if no
-
# such TimeZone instance exists. (This exists to support the use of
-
# this class with the +composed_of+ macro.)
-
1
def new(name)
-
self[name]
-
end
-
-
# Return an array of all TimeZone objects. There are multiple
-
# TimeZone objects per time zone, in many cases, to make it easier
-
# for users to find their own time zone.
-
1
def all
-
@zones ||= zones_map.values.sort
-
end
-
-
1
def zones_map
-
@zones_map ||= begin
-
new_zones_names = MAPPING.keys - lazy_zones_map.keys
-
new_zones = Hash[new_zones_names.map { |place| [place, create(place)] }]
-
-
lazy_zones_map.merge(new_zones)
-
end
-
end
-
-
# Locate a specific time zone object. If the argument is a string, it
-
# is interpreted to mean the name of the timezone to locate. If it is a
-
# numeric value it is either the hour offset, or the second offset, of the
-
# timezone to find. (The first one with that offset will be returned.)
-
# Returns +nil+ if no such time zone is known to the system.
-
1
def [](arg)
-
case arg
-
when String
-
begin
-
lazy_zones_map[arg] ||= lookup(arg).tap { |tz| tz.utc_offset }
-
rescue TZInfo::InvalidTimezoneIdentifier
-
nil
-
end
-
when Numeric, ActiveSupport::Duration
-
arg *= 3600 if arg.abs <= 13
-
all.find { |z| z.utc_offset == arg.to_i }
-
else
-
raise ArgumentError, "invalid argument to TimeZone[]: #{arg.inspect}"
-
end
-
end
-
-
# A convenience method for returning a collection of TimeZone objects
-
# for time zones in the USA.
-
1
def us_zones
-
@us_zones ||= all.find_all { |z| z.name =~ /US|Arizona|Indiana|Hawaii|Alaska/ }
-
end
-
-
1
protected
-
-
1
def require_tzinfo
-
require 'tzinfo'
-
rescue LoadError
-
$stderr.puts "You don't have tzinfo installed in your application. Please add it to your Gemfile and run bundle install"
-
raise
-
end
-
-
1
private
-
-
1
def lookup(name)
-
(tzinfo = find_tzinfo(name)) && create(tzinfo.name.freeze)
-
end
-
-
1
def lazy_zones_map
-
require_tzinfo
-
-
@lazy_zones_map ||= Hash.new do |hash, place|
-
hash[place] = create(place) if MAPPING.has_key?(place)
-
end
-
end
-
end
-
end
-
end
-
1
require 'arel/crud'
-
1
require 'arel/factory_methods'
-
-
1
require 'arel/expressions'
-
1
require 'arel/predications'
-
1
require 'arel/math'
-
1
require 'arel/alias_predication'
-
1
require 'arel/order_predications'
-
1
require 'arel/table'
-
1
require 'arel/attributes'
-
1
require 'arel/compatibility/wheres'
-
-
#### these are deprecated
-
# The Arel::Relation constant is referenced in Rails
-
1
require 'arel/relation'
-
1
require 'arel/expression'
-
####
-
-
1
require 'arel/visitors'
-
-
1
require 'arel/tree_manager'
-
1
require 'arel/insert_manager'
-
1
require 'arel/select_manager'
-
1
require 'arel/update_manager'
-
1
require 'arel/delete_manager'
-
1
require 'arel/nodes'
-
-
-
#### these are deprecated
-
1
require 'arel/deprecated'
-
1
require 'arel/sql/engine'
-
1
require 'arel/sql_literal'
-
####
-
-
1
module Arel
-
1
VERSION = '2.2.3'
-
-
1
def self.sql raw_sql
-
Arel::Nodes::SqlLiteral.new raw_sql
-
end
-
-
1
def self.star
-
sql '*'
-
end
-
## Convenience Alias
-
1
Node = Arel::Nodes::Node
-
end
-
1
module Arel
-
1
module AliasPredication
-
1
def as other
-
Nodes::As.new self, Nodes::SqlLiteral.new(other)
-
end
-
end
-
end
-
1
require 'arel/attributes/attribute'
-
-
1
module Arel
-
1
module Attributes
-
###
-
# Factory method to wrap a raw database +column+ to an Arel Attribute.
-
1
def self.for column
-
case column.type
-
when :string, :text, :binary then String
-
when :integer then Integer
-
when :float then Float
-
when :decimal then Decimal
-
when :date, :datetime, :timestamp, :time then Time
-
when :boolean then Boolean
-
else
-
Undefined
-
end
-
end
-
end
-
end
-
1
module Arel
-
1
module Attributes
-
1
class Attribute < Struct.new :relation, :name
-
1
include Arel::Expressions
-
1
include Arel::Predications
-
1
include Arel::AliasPredication
-
1
include Arel::OrderPredications
-
1
include Arel::Math
-
-
###
-
# Create a node for lowering this attribute
-
1
def lower
-
relation.lower self
-
end
-
end
-
-
1
class String < Attribute; end
-
1
class Time < Attribute; end
-
1
class Boolean < Attribute; end
-
1
class Decimal < Attribute; end
-
1
class Float < Attribute; end
-
1
class Integer < Attribute; end
-
1
class Undefined < Attribute; end
-
end
-
-
1
Attribute = Attributes::Attribute
-
end
-
1
module Arel
-
1
module Compatibility # :nodoc:
-
1
class Wheres # :nodoc:
-
1
include Enumerable
-
-
1
module Value # :nodoc:
-
1
attr_accessor :visitor
-
1
def value
-
visitor.accept self
-
end
-
-
1
def name
-
super.to_sym
-
end
-
end
-
-
1
def initialize engine, collection
-
@engine = engine
-
@collection = collection
-
end
-
-
1
def each
-
to_sql = Visitors::ToSql.new @engine
-
-
@collection.each { |c|
-
c.extend(Value)
-
c.visitor = to_sql
-
yield c
-
}
-
end
-
end
-
end
-
end
-
1
module Arel
-
###
-
# FIXME hopefully we can remove this
-
1
module Crud
-
1
def compile_update values
-
um = UpdateManager.new @engine
-
-
if Nodes::SqlLiteral === values
-
relation = @ctx.from
-
else
-
relation = values.first.first.relation
-
end
-
um.table relation
-
um.set values
-
um.take @ast.limit.expr if @ast.limit
-
um.order(*@ast.orders)
-
um.wheres = @ctx.wheres
-
um
-
end
-
-
# FIXME: this method should go away
-
1
def update values
-
if $VERBOSE
-
warn <<-eowarn
-
update (#{caller.first}) is deprecated and will be removed in ARel 3.0.0. Please
-
switch to `compile_update`
-
eowarn
-
end
-
-
um = compile_update values
-
@engine.connection.update um.to_sql, 'AREL'
-
end
-
-
1
def compile_insert values
-
im = create_insert
-
im.insert values
-
im
-
end
-
-
1
def create_insert
-
InsertManager.new @engine
-
end
-
-
# FIXME: this method should go away
-
1
def insert values
-
if $VERBOSE
-
warn <<-eowarn
-
insert (#{caller.first}) is deprecated and will be removed in ARel 3.0.0. Please
-
switch to `compile_insert`
-
eowarn
-
end
-
@engine.connection.insert compile_insert(values).to_sql
-
end
-
-
1
def compile_delete
-
dm = DeleteManager.new @engine
-
dm.wheres = @ctx.wheres
-
dm.from @ctx.froms
-
dm
-
end
-
-
1
def delete
-
if $VERBOSE
-
warn <<-eowarn
-
delete (#{caller.first}) is deprecated and will be removed in ARel 3.0.0. Please
-
switch to `compile_delete`
-
eowarn
-
end
-
@engine.connection.delete compile_delete.to_sql, 'AREL'
-
end
-
end
-
end
-
1
module Arel
-
1
class DeleteManager < Arel::TreeManager
-
1
def initialize engine
-
super
-
@ast = Nodes::DeleteStatement.new
-
@ctx = @ast
-
end
-
-
1
def from relation
-
@ast.relation = relation
-
self
-
end
-
-
1
def wheres= list
-
@ast.wheres = list
-
end
-
end
-
end
-
1
module Arel
-
1
InnerJoin = Nodes::InnerJoin
-
1
OuterJoin = Nodes::OuterJoin
-
end
-
1
module Arel
-
1
module Expression
-
1
include Arel::OrderPredications
-
end
-
end
-
1
module Arel
-
1
module Expressions
-
1
def count distinct = false
-
Nodes::Count.new [self], distinct
-
end
-
-
1
def sum
-
Nodes::Sum.new [self], Nodes::SqlLiteral.new('sum_id')
-
end
-
-
1
def maximum
-
Nodes::Max.new [self], Nodes::SqlLiteral.new('max_id')
-
end
-
-
1
def minimum
-
Nodes::Min.new [self], Nodes::SqlLiteral.new('min_id')
-
end
-
-
1
def average
-
Nodes::Avg.new [self], Nodes::SqlLiteral.new('avg_id')
-
end
-
end
-
end
-
1
module Arel
-
###
-
# Methods for creating various nodes
-
1
module FactoryMethods
-
1
def create_true
-
Arel::Nodes::True.new
-
end
-
-
1
def create_false
-
Arel::Nodes::False.new
-
end
-
-
1
def create_table_alias relation, name
-
Nodes::TableAlias.new(relation, name)
-
end
-
-
1
def create_join to, constraint = nil, klass = Nodes::InnerJoin
-
klass.new(to, constraint)
-
end
-
-
1
def create_string_join to
-
create_join to, nil, Nodes::StringJoin
-
end
-
-
1
def create_and clauses
-
Nodes::And.new clauses
-
end
-
-
1
def create_on expr
-
Nodes::On.new expr
-
end
-
-
1
def grouping expr
-
Nodes::Grouping.new expr
-
end
-
-
###
-
# Create a LOWER() function
-
1
def lower column
-
Nodes::NamedFunction.new 'LOWER', [column]
-
end
-
end
-
end
-
1
module Arel
-
1
class InsertManager < Arel::TreeManager
-
1
def initialize engine
-
super
-
@ast = Nodes::InsertStatement.new
-
end
-
-
1
def into table
-
@ast.relation = table
-
self
-
end
-
-
1
def columns; @ast.columns end
-
1
def values= val; @ast.values = val; end
-
-
1
def insert fields
-
return if fields.empty?
-
-
if String === fields
-
@ast.values = SqlLiteral.new(fields)
-
else
-
@ast.relation ||= fields.first.first.relation
-
-
values = []
-
-
fields.each do |column, value|
-
@ast.columns << column
-
values << value
-
end
-
@ast.values = create_values values, @ast.columns
-
end
-
end
-
-
1
def create_values values, columns
-
Nodes::Values.new values, columns
-
end
-
end
-
end
-
1
module Arel
-
1
module Math
-
1
def *(other)
-
Arel::Nodes::Multiplication.new(self, other)
-
end
-
-
1
def +(other)
-
Arel::Nodes::Grouping.new(Arel::Nodes::Addition.new(self, other))
-
end
-
-
1
def -(other)
-
Arel::Nodes::Grouping.new(Arel::Nodes::Subtraction.new(self, other))
-
end
-
-
1
def /(other)
-
Arel::Nodes::Division.new(self, other)
-
end
-
end
-
end
-
# node
-
1
require 'arel/nodes/node'
-
1
require 'arel/nodes/select_statement'
-
1
require 'arel/nodes/select_core'
-
1
require 'arel/nodes/insert_statement'
-
1
require 'arel/nodes/update_statement'
-
-
# terminal
-
-
1
require 'arel/nodes/terminal'
-
1
require 'arel/nodes/true'
-
1
require 'arel/nodes/false'
-
-
# unary
-
1
require 'arel/nodes/unary'
-
1
require 'arel/nodes/ascending'
-
1
require 'arel/nodes/descending'
-
1
require 'arel/nodes/unqualified_column'
-
1
require 'arel/nodes/with'
-
-
# binary
-
1
require 'arel/nodes/binary'
-
1
require 'arel/nodes/equality'
-
1
require 'arel/nodes/in' # Why is this subclassed from equality?
-
1
require 'arel/nodes/join_source'
-
1
require 'arel/nodes/delete_statement'
-
1
require 'arel/nodes/table_alias'
-
1
require 'arel/nodes/infix_operation'
-
-
# nary
-
1
require 'arel/nodes/and'
-
-
# function
-
# FIXME: Function + Alias can be rewritten as a Function and Alias node.
-
# We should make Function a Unary node and deprecate the use of "aliaz"
-
1
require 'arel/nodes/function'
-
1
require 'arel/nodes/count'
-
1
require 'arel/nodes/values'
-
1
require 'arel/nodes/named_function'
-
-
# joins
-
1
require 'arel/nodes/inner_join'
-
1
require 'arel/nodes/outer_join'
-
1
require 'arel/nodes/string_join'
-
-
1
require 'arel/nodes/sql_literal'
-
1
module Arel
-
1
module Nodes
-
1
class And < Arel::Nodes::Node
-
1
attr_reader :children
-
-
1
def initialize children, right = nil
-
unless Array === children
-
warn "(#{caller.first}) AND nodes should be created with a list"
-
children = [children, right]
-
end
-
@children = children
-
end
-
-
1
def left
-
children.first
-
end
-
-
1
def right
-
children[1]
-
end
-
end
-
end
-
end
-
1
module Arel
-
1
module Nodes
-
1
class Ascending < Ordering
-
-
1
def reverse
-
Descending.new(expr)
-
end
-
-
1
def direction
-
:asc
-
end
-
-
1
def ascending?
-
true
-
end
-
-
1
def descending?
-
false
-
end
-
-
end
-
end
-
end
-
1
module Arel
-
1
module Nodes
-
1
class Binary < Arel::Nodes::Node
-
1
attr_accessor :left, :right
-
-
1
def initialize left, right
-
@left = left
-
@right = right
-
end
-
-
1
def initialize_copy other
-
super
-
@left = @left.clone if @left
-
@right = @right.clone if @right
-
end
-
end
-
-
%w{
-
As
-
1
Assignment
-
Between
-
DoesNotMatch
-
GreaterThan
-
GreaterThanOrEqual
-
Join
-
LessThan
-
LessThanOrEqual
-
Matches
-
NotEqual
-
NotIn
-
Or
-
Union
-
UnionAll
-
Intersect
-
Except
-
}.each do |name|
-
17
const_set name, Class.new(Binary)
-
end
-
end
-
end
-
1
module Arel
-
1
module Nodes
-
1
class Count < Arel::Nodes::Function
-
1
def initialize expr, distinct = false, aliaz = nil
-
super(expr, aliaz)
-
@distinct = distinct
-
end
-
end
-
end
-
end
-
1
module Arel
-
1
module Nodes
-
1
class DeleteStatement < Arel::Nodes::Binary
-
1
alias :relation :left
-
1
alias :relation= :left=
-
1
alias :wheres :right
-
1
alias :wheres= :right=
-
-
1
def initialize relation = nil, wheres = []
-
super
-
end
-
-
1
def initialize_copy other
-
super
-
@right = @right.clone
-
end
-
end
-
end
-
end
-
1
module Arel
-
1
module Nodes
-
1
class Descending < Ordering
-
-
1
def reverse
-
Ascending.new(expr)
-
end
-
-
1
def direction
-
:desc
-
end
-
-
1
def ascending?
-
false
-
end
-
-
1
def descending?
-
true
-
end
-
-
end
-
end
-
end
-
1
module Arel
-
1
module Nodes
-
1
class Equality < Arel::Nodes::Binary
-
1
def operator; :== end
-
1
alias :operand1 :left
-
1
alias :operand2 :right
-
end
-
end
-
end
-
1
module Arel
-
1
module Nodes
-
1
class False < Arel::Nodes::Node
-
end
-
end
-
end
-
1
module Arel
-
1
module Nodes
-
1
class Function < Arel::Nodes::Node
-
1
include Arel::Expression
-
1
include Arel::Predications
-
1
attr_accessor :expressions, :alias, :distinct
-
-
1
def initialize expr, aliaz = nil
-
@expressions = expr
-
@alias = aliaz && SqlLiteral.new(aliaz)
-
@distinct = false
-
end
-
-
1
def as aliaz
-
self.alias = SqlLiteral.new(aliaz)
-
self
-
end
-
end
-
-
%w{
-
Sum
-
1
Exists
-
Max
-
Min
-
Avg
-
}.each do |name|
-
5
const_set(name, Class.new(Function))
-
end
-
end
-
end
-
1
module Arel
-
1
module Nodes
-
1
class In < Equality
-
end
-
end
-
end
-
1
module Arel
-
1
module Nodes
-
-
1
class InfixOperation < Binary
-
1
include Arel::Expressions
-
1
include Arel::Predications
-
1
include Arel::OrderPredications
-
1
include Arel::AliasPredication
-
1
include Arel::Math
-
-
1
attr_reader :operator
-
-
1
def initialize operator, left, right
-
super(left, right)
-
@operator = operator
-
end
-
end
-
-
1
class Multiplication < InfixOperation
-
1
def initialize left, right
-
super(:*, left, right)
-
end
-
end
-
-
1
class Division < InfixOperation
-
1
def initialize left, right
-
super(:/, left, right)
-
end
-
end
-
-
1
class Addition < InfixOperation
-
1
def initialize left, right
-
super(:+, left, right)
-
end
-
end
-
-
1
class Subtraction < InfixOperation
-
1
def initialize left, right
-
super(:-, left, right)
-
end
-
end
-
-
end
-
end
-
1
module Arel
-
1
module Nodes
-
1
class InnerJoin < Arel::Nodes::Join
-
end
-
end
-
end
-
1
module Arel
-
1
module Nodes
-
1
class InsertStatement < Arel::Nodes::Node
-
1
attr_accessor :relation, :columns, :values
-
-
1
def initialize
-
@relation = nil
-
@columns = []
-
@values = nil
-
end
-
-
1
def initialize_copy other
-
super
-
@columns = @columns.clone
-
@values = @values.clone if @values
-
end
-
end
-
end
-
end
-
1
module Arel
-
1
module Nodes
-
###
-
# Class that represents a join source
-
#
-
# http://www.sqlite.org/syntaxdiagrams.html#join-source
-
-
1
class JoinSource < Arel::Nodes::Binary
-
1
def initialize single_source, joinop = []
-
super
-
end
-
-
1
def empty?
-
!left && right.empty?
-
end
-
end
-
end
-
end
-
1
module Arel
-
1
module Nodes
-
1
class NamedFunction < Arel::Nodes::Function
-
1
attr_accessor :name
-
-
1
def initialize name, expr, aliaz = nil
-
super(expr, aliaz)
-
@name = name
-
end
-
end
-
end
-
end
-
1
module Arel
-
1
module Nodes
-
###
-
# Abstract base class for all AST nodes
-
1
class Node
-
1
include Arel::FactoryMethods
-
1
include Enumerable
-
-
###
-
# Factory method to create a Nodes::Not node that has the recipient of
-
# the caller as a child.
-
1
def not
-
Nodes::Not.new self
-
end
-
-
###
-
# Factory method to create a Nodes::Grouping node that has an Nodes::Or
-
# node as a child.
-
1
def or right
-
Nodes::Grouping.new Nodes::Or.new(self, right)
-
end
-
-
###
-
# Factory method to create an Nodes::And node.
-
1
def and right
-
Nodes::And.new [self, right]
-
end
-
-
# FIXME: this method should go away. I don't like people calling
-
# to_sql on non-head nodes. This forces us to walk the AST until we
-
# can find a node that has a "relation" member.
-
#
-
# Maybe we should just use `Table.engine`? :'(
-
1
def to_sql engine = Table.engine
-
engine.connection.visitor.accept self
-
end
-
-
# Iterate through AST, nodes will be yielded depth-first
-
1
def each &block
-
return enum_for(:each) unless block_given?
-
-
::Arel::Visitors::DepthFirst.new(block).accept self
-
end
-
end
-
end
-
end
-
1
module Arel
-
1
module Nodes
-
1
class OuterJoin < Arel::Nodes::Join
-
end
-
end
-
end
-
1
module Arel
-
1
module Nodes
-
1
class SelectCore < Arel::Nodes::Node
-
1
attr_accessor :top, :projections, :wheres, :groups
-
1
attr_accessor :having, :source, :set_quantifier
-
-
1
def initialize
-
@source = JoinSource.new nil
-
@top = nil
-
-
# http://savage.net.au/SQL/sql-92.bnf.html#set%20quantifier
-
@set_quantifier = nil
-
@projections = []
-
@wheres = []
-
@groups = []
-
@having = nil
-
end
-
-
1
def from
-
@source.left
-
end
-
-
1
def from= value
-
@source.left = value
-
end
-
-
1
alias :froms= :from=
-
1
alias :froms :from
-
-
1
def initialize_copy other
-
super
-
@source = @source.clone if @source
-
@projections = @projections.clone
-
@wheres = @wheres.clone
-
@groups = @groups.clone
-
@having = @having.clone if @having
-
end
-
end
-
end
-
end
-
1
module Arel
-
1
module Nodes
-
1
class SelectStatement < Arel::Nodes::Node
-
1
attr_reader :cores
-
1
attr_accessor :limit, :orders, :lock, :offset, :with
-
-
1
def initialize cores = [SelectCore.new]
-
#puts caller
-
@cores = cores
-
@orders = []
-
@limit = nil
-
@lock = nil
-
@offset = nil
-
@with = nil
-
end
-
-
1
def initialize_copy other
-
super
-
@cores = @cores.map { |x| x.clone }
-
@orders = @orders.map { |x| x.clone }
-
end
-
end
-
end
-
end
-
1
module Arel
-
1
module Nodes
-
1
class SqlLiteral < String
-
1
include Arel::Expressions
-
1
include Arel::Predications
-
1
include Arel::AliasPredication
-
1
include Arel::OrderPredications
-
end
-
-
1
class BindParam < SqlLiteral
-
end
-
end
-
end
-
1
module Arel
-
1
module Nodes
-
1
class StringJoin < Arel::Nodes::Join
-
1
def initialize left, right = nil
-
super
-
end
-
end
-
end
-
end
-
1
module Arel
-
1
module Nodes
-
1
class TableAlias < Arel::Nodes::Binary
-
1
alias :name :right
-
1
alias :relation :left
-
1
alias :table_alias :name
-
-
1
def [] name
-
Attribute.new(self, name)
-
end
-
-
1
def table_name
-
relation.respond_to?(:name) ? relation.name : name
-
end
-
end
-
end
-
end
-
1
module Arel
-
1
module Nodes
-
1
class Distinct < Arel::Nodes::Node
-
end
-
end
-
end
-
1
module Arel
-
1
module Nodes
-
1
class True < Arel::Nodes::Node
-
end
-
end
-
end
-
1
module Arel
-
1
module Nodes
-
1
class Unary < Arel::Nodes::Node
-
1
attr_accessor :expr
-
1
alias :value :expr
-
-
1
def initialize expr
-
@expr = expr
-
end
-
end
-
-
%w{
-
Bin
-
1
Group
-
Grouping
-
Having
-
Limit
-
Not
-
Offset
-
On
-
Ordering
-
Top
-
Lock
-
DistinctOn
-
}.each do |name|
-
12
const_set(name, Class.new(Unary))
-
end
-
end
-
end
-
1
module Arel
-
1
module Nodes
-
1
class UnqualifiedColumn < Arel::Nodes::Unary
-
1
alias :attribute :expr
-
1
alias :attribute= :expr=
-
-
1
def relation
-
@expr.relation
-
end
-
-
1
def column
-
@expr.column
-
end
-
-
1
def name
-
@expr.name
-
end
-
end
-
end
-
end
-
1
module Arel
-
1
module Nodes
-
1
class UpdateStatement < Arel::Nodes::Node
-
1
attr_accessor :relation, :wheres, :values, :orders, :limit
-
1
attr_accessor :key
-
-
1
def initialize
-
@relation = nil
-
@wheres = []
-
@values = []
-
@orders = []
-
@limit = nil
-
@key = nil
-
end
-
-
1
def initialize_copy other
-
super
-
@wheres = @wheres.clone
-
@values = @values.clone
-
end
-
end
-
end
-
end
-
1
module Arel
-
1
module Nodes
-
1
class Values < Arel::Nodes::Binary
-
1
alias :expressions :left
-
1
alias :expressions= :left=
-
1
alias :columns :right
-
1
alias :columns= :right=
-
-
1
def initialize exprs, columns = []
-
super
-
end
-
end
-
end
-
end
-
1
module Arel
-
1
module Nodes
-
1
class With < Arel::Nodes::Unary
-
1
alias children expr
-
end
-
-
1
class WithRecursive < With; end
-
end
-
end
-
-
1
module Arel
-
1
module OrderPredications
-
-
1
def asc
-
Nodes::Ascending.new self
-
end
-
-
1
def desc
-
Nodes::Descending.new self
-
end
-
-
end
-
end
-
1
module Arel
-
1
module Predications
-
1
def not_eq other
-
Nodes::NotEqual.new self, other
-
end
-
-
1
def not_eq_any others
-
grouping_any :not_eq, others
-
end
-
-
1
def not_eq_all others
-
grouping_all :not_eq, others
-
end
-
-
1
def eq other
-
Nodes::Equality.new self, other
-
end
-
-
1
def eq_any others
-
grouping_any :eq, others
-
end
-
-
1
def eq_all others
-
grouping_all :eq, others
-
end
-
-
1
def in other
-
case other
-
when Arel::SelectManager
-
Arel::Nodes::In.new(self, other.ast)
-
when Range
-
if other.exclude_end?
-
left = Nodes::GreaterThanOrEqual.new(self, other.begin)
-
right = Nodes::LessThan.new(self, other.end)
-
Nodes::And.new [left, right]
-
else
-
Nodes::Between.new(self, Nodes::And.new([other.begin, other.end]))
-
end
-
else
-
Nodes::In.new self, other
-
end
-
end
-
-
1
def in_any others
-
grouping_any :in, others
-
end
-
-
1
def in_all others
-
grouping_all :in, others
-
end
-
-
1
def not_in other
-
case other
-
when Arel::SelectManager
-
Arel::Nodes::NotIn.new(self, other.ast)
-
when Range
-
if other.exclude_end?
-
left = Nodes::LessThan.new(self, other.begin)
-
right = Nodes::GreaterThanOrEqual.new(self, other.end)
-
Nodes::Or.new left, right
-
else
-
left = Nodes::LessThan.new(self, other.begin)
-
right = Nodes::GreaterThan.new(self, other.end)
-
Nodes::Or.new left, right
-
end
-
else
-
Nodes::NotIn.new self, other
-
end
-
end
-
-
1
def not_in_any others
-
grouping_any :not_in, others
-
end
-
-
1
def not_in_all others
-
grouping_all :not_in, others
-
end
-
-
1
def matches other
-
Nodes::Matches.new self, other
-
end
-
-
1
def matches_any others
-
grouping_any :matches, others
-
end
-
-
1
def matches_all others
-
grouping_all :matches, others
-
end
-
-
1
def does_not_match other
-
Nodes::DoesNotMatch.new self, other
-
end
-
-
1
def does_not_match_any others
-
grouping_any :does_not_match, others
-
end
-
-
1
def does_not_match_all others
-
grouping_all :does_not_match, others
-
end
-
-
1
def gteq right
-
Nodes::GreaterThanOrEqual.new self, right
-
end
-
-
1
def gteq_any others
-
grouping_any :gteq, others
-
end
-
-
1
def gteq_all others
-
grouping_all :gteq, others
-
end
-
-
1
def gt right
-
Nodes::GreaterThan.new self, right
-
end
-
-
1
def gt_any others
-
grouping_any :gt, others
-
end
-
-
1
def gt_all others
-
grouping_all :gt, others
-
end
-
-
1
def lt right
-
Nodes::LessThan.new self, right
-
end
-
-
1
def lt_any others
-
grouping_any :lt, others
-
end
-
-
1
def lt_all others
-
grouping_all :lt, others
-
end
-
-
1
def lteq right
-
Nodes::LessThanOrEqual.new self, right
-
end
-
-
1
def lteq_any others
-
grouping_any :lteq, others
-
end
-
-
1
def lteq_all others
-
grouping_all :lteq, others
-
end
-
-
1
private
-
-
1
def grouping_any method_id, others
-
nodes = others.map {|expr| send(method_id, expr)}
-
Nodes::Grouping.new nodes.inject { |memo,node|
-
Nodes::Or.new(memo, node)
-
}
-
end
-
-
1
def grouping_all method_id, others
-
Nodes::Grouping.new Nodes::And.new(others.map {|expr| send(method_id, expr)})
-
end
-
end
-
end
-
1
module Arel
-
###
-
# This is deprecated. Fix rails, then remove this.
-
1
module Relation
-
end
-
end
-
1
module Arel
-
1
class SelectManager < Arel::TreeManager
-
1
include Arel::Crud
-
-
1
def initialize engine, table = nil
-
super(engine)
-
@ast = Nodes::SelectStatement.new
-
@ctx = @ast.cores.last
-
from table
-
end
-
-
1
def initialize_copy other
-
super
-
@ctx = @ast.cores.last
-
end
-
-
1
def limit
-
@ast.limit && @ast.limit.expr
-
end
-
1
alias :taken :limit
-
-
1
def constraints
-
@ctx.wheres
-
end
-
-
1
def offset
-
@ast.offset && @ast.offset.expr
-
end
-
-
1
def skip amount
-
if amount
-
@ast.offset = Nodes::Offset.new(amount)
-
else
-
@ast.offset = nil
-
end
-
self
-
end
-
1
alias :offset= :skip
-
-
###
-
# Produces an Arel::Nodes::Exists node
-
1
def exists
-
Arel::Nodes::Exists.new @ast
-
end
-
-
1
def as other
-
create_table_alias grouping(@ast), Nodes::SqlLiteral.new(other)
-
end
-
-
1
def where_clauses
-
if $VERBOSE
-
warn "(#{caller.first}) where_clauses is deprecated and will be removed in arel 3.0.0 with no replacement"
-
end
-
to_sql = Visitors::ToSql.new @engine.connection_pool
-
@ctx.wheres.map { |c| to_sql.accept c }
-
end
-
-
1
def lock locking = Arel.sql('FOR UPDATE')
-
case locking
-
when true
-
locking = Arel.sql('FOR UPDATE')
-
when Arel::Nodes::SqlLiteral
-
when String
-
locking = Arel.sql locking
-
end
-
-
@ast.lock = Nodes::Lock.new(locking)
-
self
-
end
-
-
1
def locked
-
@ast.lock
-
end
-
-
1
def on *exprs
-
@ctx.source.right.last.right = Nodes::On.new(collapse(exprs))
-
self
-
end
-
-
1
def group *columns
-
columns.each do |column|
-
# FIXME: backwards compat
-
column = Nodes::SqlLiteral.new(column) if String === column
-
column = Nodes::SqlLiteral.new(column.to_s) if Symbol === column
-
-
@ctx.groups.push Nodes::Group.new column
-
end
-
self
-
end
-
-
1
def from table
-
table = Nodes::SqlLiteral.new(table) if String === table
-
# FIXME: this is a hack to support
-
# test_with_two_tables_in_from_without_getting_double_quoted
-
# from the AR tests.
-
-
case table
-
when Nodes::Join
-
@ctx.source.right << table
-
else
-
@ctx.source.left = table
-
end
-
-
self
-
end
-
-
1
def froms
-
@ast.cores.map { |x| x.from }.compact
-
end
-
-
1
def join relation, klass = Nodes::InnerJoin
-
return self unless relation
-
-
case relation
-
when String, Nodes::SqlLiteral
-
raise if relation.blank?
-
klass = Nodes::StringJoin
-
end
-
-
@ctx.source.right << create_join(relation, nil, klass)
-
self
-
end
-
-
1
def having *exprs
-
@ctx.having = Nodes::Having.new(collapse(exprs, @ctx.having))
-
self
-
end
-
-
1
def project *projections
-
# FIXME: converting these to SQLLiterals is probably not good, but
-
# rails tests require it.
-
@ctx.projections.concat projections.map { |x|
-
[Symbol, String].include?(x.class) ? SqlLiteral.new(x.to_s) : x
-
}
-
self
-
end
-
-
1
def projections= projections
-
@ctx.projections = projections
-
end
-
-
1
def distinct(value = true)
-
if value
-
@ctx.set_quantifier = Arel::Nodes::Distinct.new
-
else
-
@ctx.set_quantifier = nil
-
end
-
end
-
-
1
def order *expr
-
# FIXME: We SHOULD NOT be converting these to SqlLiteral automatically
-
@ast.orders.concat expr.map { |x|
-
String === x || Symbol === x ? Nodes::SqlLiteral.new(x.to_s) : x
-
}
-
self
-
end
-
-
1
def orders
-
@ast.orders
-
end
-
-
1
def wheres
-
warn "#{caller[0]}: SelectManager#wheres is deprecated and will be removed in ARel 3.0.0 with no replacement"
-
Compatibility::Wheres.new @engine.connection_pool, @ctx.wheres
-
end
-
-
1
def where_sql
-
return if @ctx.wheres.empty?
-
-
viz = Visitors::WhereSql.new @engine.connection_pool
-
Nodes::SqlLiteral.new viz.accept @ctx
-
end
-
-
1
def union operation, other = nil
-
if other
-
node_class = Nodes.const_get("Union#{operation.to_s.capitalize}")
-
else
-
other = operation
-
node_class = Nodes::Union
-
end
-
-
node_class.new self.ast, other.ast
-
end
-
-
1
def intersect other
-
Nodes::Intersect.new ast, other.ast
-
end
-
-
1
def except other
-
Nodes::Except.new ast, other.ast
-
end
-
1
alias :minus :except
-
-
1
def with *subqueries
-
if subqueries.first.is_a? Symbol
-
node_class = Nodes.const_get("With#{subqueries.shift.to_s.capitalize}")
-
else
-
node_class = Nodes::With
-
end
-
@ast.with = node_class.new(subqueries.flatten)
-
-
self
-
end
-
-
1
def take limit
-
if limit
-
@ast.limit = Nodes::Limit.new(limit)
-
@ctx.top = Nodes::Top.new(limit)
-
else
-
@ast.limit = nil
-
@ctx.top = nil
-
end
-
self
-
end
-
1
alias limit= take
-
-
1
def join_sql
-
return nil if @ctx.source.right.empty?
-
-
sql = visitor.dup.extend(Visitors::JoinSql).accept @ctx
-
Nodes::SqlLiteral.new sql
-
end
-
-
1
def order_clauses
-
visitor = Visitors::OrderClauses.new(@engine.connection_pool)
-
visitor.accept(@ast).map { |x|
-
Nodes::SqlLiteral.new x
-
}
-
end
-
-
1
def join_sources
-
@ctx.source.right
-
end
-
-
1
def source
-
@ctx.source
-
end
-
-
1
def joins manager
-
if $VERBOSE
-
warn "joins is deprecated and will be removed in 3.0.0"
-
warn "please remove your call to joins from #{caller.first}"
-
end
-
manager.join_sql
-
end
-
-
1
class Row < Struct.new(:data) # :nodoc:
-
1
def id
-
data['id']
-
end
-
-
1
def method_missing(name, *args)
-
name = name.to_s
-
return data[name] if data.key?(name)
-
super
-
end
-
end
-
-
1
def to_a # :nodoc:
-
warn "to_a is deprecated. Please remove it from #{caller[0]}"
-
# FIXME: I think `select` should be made public...
-
@engine.connection.send(:select, to_sql, 'AREL').map { |x| Row.new(x) }
-
end
-
-
# FIXME: this method should go away
-
1
def insert values
-
if $VERBOSE
-
warn <<-eowarn
-
insert (#{caller.first}) is deprecated and will be removed in ARel 3.0.0. Please
-
switch to `compile_insert`
-
eowarn
-
end
-
-
im = compile_insert(values)
-
table = @ctx.froms
-
-
primary_key = table.primary_key
-
primary_key_name = primary_key.name if primary_key
-
-
# FIXME: in AR tests values sometimes were Array and not Hash therefore is_a?(Hash) check is added
-
primary_key_value = primary_key && values.is_a?(Hash) && values[primary_key]
-
im.into table
-
# Oracle adapter needs primary key name to generate RETURNING ... INTO ... clause
-
# for tables which assign primary key value using trigger.
-
# RETURNING ... INTO ... clause will be added only if primary_key_value is nil
-
# therefore it is necessary to pass primary key value as well
-
@engine.connection.insert im.to_sql, 'AREL', primary_key_name, primary_key_value
-
end
-
-
1
private
-
1
def collapse exprs, existing = nil
-
exprs = exprs.unshift(existing.expr) if existing
-
exprs = exprs.compact.map { |expr|
-
if String === expr
-
# FIXME: Don't do this automatically
-
Arel.sql(expr)
-
else
-
expr
-
end
-
}
-
-
if exprs.length == 1
-
exprs.first
-
else
-
create_and exprs
-
end
-
end
-
end
-
end
-
1
module Arel
-
1
module Sql
-
1
class Engine
-
1
def self.new thing
-
#warn "#{caller.first} -- Engine will be removed"
-
thing
-
end
-
end
-
end
-
end
-
1
module Arel
-
1
class SqlLiteral < Nodes::SqlLiteral
-
end
-
end
-
1
module Arel
-
1
class Table
-
1
include Arel::Crud
-
1
include Arel::FactoryMethods
-
-
1
@engine = nil
-
2
class << self; attr_accessor :engine; end
-
-
1
attr_accessor :name, :engine, :aliases, :table_alias
-
-
# TableAlias and Table both have a #table_name which is the name of the underlying table
-
1
alias :table_name :name
-
-
1
def initialize name, engine = Table.engine
-
@name = name.to_s
-
@engine = engine
-
@columns = nil
-
@aliases = []
-
@table_alias = nil
-
@primary_key = nil
-
-
if Hash === engine
-
@engine = engine[:engine] || Table.engine
-
-
# Sometime AR sends an :as parameter to table, to let the table know
-
# that it is an Alias. We may want to override new, and return a
-
# TableAlias node?
-
@table_alias = engine[:as] unless engine[:as].to_s == @name
-
end
-
end
-
-
1
def primary_key
-
if $VERBOSE
-
warn <<-eowarn
-
primary_key (#{caller.first}) is deprecated and will be removed in ARel 3.0.0
-
eowarn
-
end
-
@primary_key ||= begin
-
primary_key_name = @engine.connection.primary_key(name)
-
# some tables might be without primary key
-
primary_key_name && self[primary_key_name]
-
end
-
end
-
-
1
def alias name = "#{self.name}_2"
-
Nodes::TableAlias.new(self, name).tap do |node|
-
@aliases << node
-
end
-
end
-
-
1
def from table
-
SelectManager.new(@engine, table)
-
end
-
-
1
def joins manager
-
if $VERBOSE
-
warn "joins is deprecated and will be removed in 3.0.0"
-
warn "please remove your call to joins from #{caller.first}"
-
end
-
nil
-
end
-
-
1
def join relation, klass = Nodes::InnerJoin
-
return from(self) unless relation
-
-
case relation
-
when String, Nodes::SqlLiteral
-
raise if relation.blank?
-
klass = Nodes::StringJoin
-
end
-
-
from(self).join(relation, klass)
-
end
-
-
1
def group *columns
-
from(self).group(*columns)
-
end
-
-
1
def order *expr
-
from(self).order(*expr)
-
end
-
-
1
def where condition
-
from(self).where condition
-
end
-
-
1
def project *things
-
from(self).project(*things)
-
end
-
-
1
def take amount
-
from(self).take amount
-
end
-
-
1
def skip amount
-
from(self).skip amount
-
end
-
-
1
def having expr
-
from(self).having expr
-
end
-
-
1
def columns
-
if $VERBOSE
-
warn <<-eowarn
-
(#{caller.first}) Arel::Table#columns is deprecated and will be removed in
-
Arel 3.0.0 with no replacement. PEW PEW PEW!!!
-
eowarn
-
end
-
@columns ||=
-
attributes_for @engine.connection.columns(@name, "#{@name} Columns")
-
end
-
-
1
def [] name
-
::Arel::Attribute.new self, name
-
end
-
-
1
def select_manager
-
SelectManager.new(@engine)
-
end
-
-
1
def insert_manager
-
InsertManager.new(@engine)
-
end
-
-
1
private
-
-
1
def attributes_for columns
-
return nil unless columns
-
-
columns.map do |column|
-
Attributes.for(column).new self, column.name.to_sym
-
end
-
end
-
-
1
@@table_cache = nil
-
1
def self.table_cache engine # :nodoc:
-
if $VERBOSE
-
warn <<-eowarn
-
(#{caller.first}) Arel::Table.table_cache is deprecated and will be removed in
-
Arel 3.0.0 with no replacement. PEW PEW PEW!!!
-
eowarn
-
end
-
@@table_cache ||= Hash[engine.connection.tables.map { |x| [x,true] }]
-
end
-
end
-
end
-
1
module Arel
-
1
class TreeManager
-
# FIXME: Remove this.
-
1
include Arel::Relation
-
1
include Arel::FactoryMethods
-
-
1
attr_reader :ast, :engine
-
-
1
def initialize engine
-
@engine = engine
-
@ctx = nil
-
end
-
-
1
def to_dot
-
Visitors::Dot.new.accept @ast
-
end
-
-
1
def visitor
-
engine.connection.visitor
-
end
-
-
1
def to_sql
-
visitor.accept @ast
-
end
-
-
1
def initialize_copy other
-
super
-
@ast = @ast.clone
-
end
-
-
1
def where expr
-
if Arel::TreeManager === expr
-
expr = expr.ast
-
end
-
@ctx.wheres << expr
-
self
-
end
-
end
-
end
-
1
module Arel
-
1
class UpdateManager < Arel::TreeManager
-
1
def initialize engine
-
super
-
@ast = Nodes::UpdateStatement.new
-
@ctx = @ast
-
end
-
-
1
def take limit
-
@ast.limit = Nodes::Limit.new(limit) if limit
-
self
-
end
-
-
1
def key= key
-
@ast.key = key
-
end
-
-
1
def key
-
@ast.key
-
end
-
-
1
def order *expr
-
@ast.orders = expr
-
self
-
end
-
-
###
-
# UPDATE +table+
-
1
def table table
-
@ast.relation = table
-
self
-
end
-
-
1
def wheres= exprs
-
@ast.wheres = exprs
-
end
-
-
1
def where expr
-
@ast.wheres << expr
-
self
-
end
-
-
1
def set values
-
if String === values
-
@ast.values = [values]
-
else
-
@ast.values = values.map { |column,value|
-
Nodes::Assignment.new(
-
Nodes::UnqualifiedColumn.new(column),
-
value
-
)
-
}
-
end
-
self
-
end
-
end
-
end
-
1
require 'arel/visitors/visitor'
-
1
require 'arel/visitors/depth_first'
-
1
require 'arel/visitors/to_sql'
-
1
require 'arel/visitors/sqlite'
-
1
require 'arel/visitors/postgresql'
-
1
require 'arel/visitors/mysql'
-
1
require 'arel/visitors/mssql'
-
1
require 'arel/visitors/oracle'
-
1
require 'arel/visitors/join_sql'
-
1
require 'arel/visitors/where_sql'
-
1
require 'arel/visitors/order_clauses'
-
1
require 'arel/visitors/dot'
-
1
require 'arel/visitors/ibm_db'
-
1
require 'arel/visitors/informix'
-
-
1
module Arel
-
1
module Visitors
-
1
VISITORS = {
-
'postgresql' => Arel::Visitors::PostgreSQL,
-
'mysql' => Arel::Visitors::MySQL,
-
'mysql2' => Arel::Visitors::MySQL,
-
'mssql' => Arel::Visitors::MSSQL,
-
'sqlserver' => Arel::Visitors::MSSQL,
-
'oracle_enhanced' => Arel::Visitors::Oracle,
-
'sqlite' => Arel::Visitors::SQLite,
-
'sqlite3' => Arel::Visitors::SQLite,
-
'ibm_db' => Arel::Visitors::IBM_DB,
-
'informix' => Arel::Visitors::Informix,
-
}
-
-
1
ENGINE_VISITORS = Hash.new do |hash, engine|
-
pool = engine.connection_pool
-
adapter = pool.spec.config[:adapter]
-
hash[engine] = (VISITORS[adapter] || Visitors::ToSql).new(engine)
-
end
-
-
1
def self.visitor_for engine
-
ENGINE_VISITORS[engine]
-
end
-
2
class << self; alias :for :visitor_for; end
-
end
-
end
-
1
module Arel
-
1
module Visitors
-
1
class DepthFirst < Arel::Visitors::Visitor
-
1
def initialize block = nil
-
@block = block || Proc.new
-
end
-
-
1
private
-
-
1
def visit o
-
super
-
@block.call o
-
end
-
-
1
def unary o
-
visit o.expr
-
end
-
1
alias :visit_Arel_Nodes_Group :unary
-
1
alias :visit_Arel_Nodes_Grouping :unary
-
1
alias :visit_Arel_Nodes_Having :unary
-
1
alias :visit_Arel_Nodes_Limit :unary
-
1
alias :visit_Arel_Nodes_Not :unary
-
1
alias :visit_Arel_Nodes_Offset :unary
-
1
alias :visit_Arel_Nodes_On :unary
-
1
alias :visit_Arel_Nodes_Ordering :unary
-
1
alias :visit_Arel_Nodes_Ascending :unary
-
1
alias :visit_Arel_Nodes_Descending :unary
-
1
alias :visit_Arel_Nodes_Top :unary
-
1
alias :visit_Arel_Nodes_UnqualifiedColumn :unary
-
-
1
def function o
-
visit o.expressions
-
visit o.alias
-
visit o.distinct
-
end
-
1
alias :visit_Arel_Nodes_Avg :function
-
1
alias :visit_Arel_Nodes_Exists :function
-
1
alias :visit_Arel_Nodes_Max :function
-
1
alias :visit_Arel_Nodes_Min :function
-
1
alias :visit_Arel_Nodes_Sum :function
-
-
1
def visit_Arel_Nodes_NamedFunction o
-
visit o.name
-
visit o.expressions
-
visit o.distinct
-
visit o.alias
-
end
-
-
1
def visit_Arel_Nodes_Count o
-
visit o.expressions
-
visit o.alias
-
visit o.distinct
-
end
-
-
1
def nary o
-
o.children.each { |child| visit child }
-
end
-
1
alias :visit_Arel_Nodes_And :nary
-
-
1
def binary o
-
visit o.left
-
visit o.right
-
end
-
1
alias :visit_Arel_Nodes_As :binary
-
1
alias :visit_Arel_Nodes_Assignment :binary
-
1
alias :visit_Arel_Nodes_Between :binary
-
1
alias :visit_Arel_Nodes_DeleteStatement :binary
-
1
alias :visit_Arel_Nodes_DoesNotMatch :binary
-
1
alias :visit_Arel_Nodes_Equality :binary
-
1
alias :visit_Arel_Nodes_GreaterThan :binary
-
1
alias :visit_Arel_Nodes_GreaterThanOrEqual :binary
-
1
alias :visit_Arel_Nodes_In :binary
-
1
alias :visit_Arel_Nodes_JoinSource :binary
-
1
alias :visit_Arel_Nodes_InnerJoin :binary
-
1
alias :visit_Arel_Nodes_LessThan :binary
-
1
alias :visit_Arel_Nodes_LessThanOrEqual :binary
-
1
alias :visit_Arel_Nodes_Matches :binary
-
1
alias :visit_Arel_Nodes_NotEqual :binary
-
1
alias :visit_Arel_Nodes_NotIn :binary
-
1
alias :visit_Arel_Nodes_Or :binary
-
1
alias :visit_Arel_Nodes_OuterJoin :binary
-
1
alias :visit_Arel_Nodes_TableAlias :binary
-
1
alias :visit_Arel_Nodes_Values :binary
-
-
1
def visit_Arel_Nodes_StringJoin o
-
visit o.left
-
end
-
-
1
def visit_Arel_Attribute o
-
visit o.relation
-
visit o.name
-
end
-
1
alias :visit_Arel_Attributes_Integer :visit_Arel_Attribute
-
1
alias :visit_Arel_Attributes_Float :visit_Arel_Attribute
-
1
alias :visit_Arel_Attributes_String :visit_Arel_Attribute
-
1
alias :visit_Arel_Attributes_Time :visit_Arel_Attribute
-
1
alias :visit_Arel_Attributes_Boolean :visit_Arel_Attribute
-
1
alias :visit_Arel_Attributes_Attribute :visit_Arel_Attribute
-
1
alias :visit_Arel_Attributes_Decimal :visit_Arel_Attribute
-
-
1
def visit_Arel_Table o
-
visit o.name
-
end
-
-
1
def terminal o
-
end
-
1
alias :visit_ActiveSupport_Multibyte_Chars :terminal
-
1
alias :visit_ActiveSupport_StringInquirer :terminal
-
1
alias :visit_Arel_Nodes_Lock :terminal
-
1
alias :visit_Arel_Nodes_Node :terminal
-
1
alias :visit_Arel_Nodes_SqlLiteral :terminal
-
1
alias :visit_Arel_Nodes_BindParam :terminal
-
1
alias :visit_Arel_SqlLiteral :terminal
-
1
alias :visit_BigDecimal :terminal
-
1
alias :visit_Bignum :terminal
-
1
alias :visit_Class :terminal
-
1
alias :visit_Date :terminal
-
1
alias :visit_DateTime :terminal
-
1
alias :visit_FalseClass :terminal
-
1
alias :visit_Fixnum :terminal
-
1
alias :visit_Float :terminal
-
1
alias :visit_NilClass :terminal
-
1
alias :visit_String :terminal
-
1
alias :visit_Symbol :terminal
-
1
alias :visit_Time :terminal
-
1
alias :visit_TrueClass :terminal
-
-
1
def visit_Arel_Nodes_InsertStatement o
-
visit o.relation
-
visit o.columns
-
visit o.values
-
end
-
-
1
def visit_Arel_Nodes_SelectCore o
-
visit o.projections
-
visit o.source
-
visit o.wheres
-
visit o.groups
-
visit o.having
-
end
-
-
1
def visit_Arel_Nodes_SelectStatement o
-
visit o.cores
-
visit o.orders
-
visit o.limit
-
visit o.lock
-
visit o.offset
-
end
-
-
1
def visit_Arel_Nodes_UpdateStatement o
-
visit o.relation
-
visit o.values
-
visit o.wheres
-
visit o.orders
-
visit o.limit
-
end
-
-
1
def visit_Array o
-
o.each { |i| visit i }
-
end
-
-
1
def visit_Hash o
-
o.each { |k,v| visit(k); visit(v) }
-
end
-
end
-
end
-
end
-
1
module Arel
-
1
module Visitors
-
1
class Dot < Arel::Visitors::Visitor
-
1
class Node # :nodoc:
-
1
attr_accessor :name, :id, :fields
-
-
1
def initialize name, id, fields = []
-
@name = name
-
@id = id
-
@fields = fields
-
end
-
end
-
-
1
class Edge < Struct.new :name, :from, :to # :nodoc:
-
end
-
-
1
def initialize
-
@nodes = []
-
@edges = []
-
@node_stack = []
-
@edge_stack = []
-
@seen = {}
-
end
-
-
1
def accept object
-
super
-
to_dot
-
end
-
-
1
private
-
1
def visit_Arel_Nodes_Ordering o
-
visit_edge o, "expr"
-
end
-
-
1
def visit_Arel_Nodes_TableAlias o
-
visit_edge o, "name"
-
visit_edge o, "relation"
-
end
-
-
1
def visit_Arel_Nodes_Count o
-
visit_edge o, "expressions"
-
visit_edge o, "distinct"
-
end
-
-
1
def visit_Arel_Nodes_Values o
-
visit_edge o, "expressions"
-
end
-
-
1
def visit_Arel_Nodes_StringJoin o
-
visit_edge o, "left"
-
end
-
-
1
def visit_Arel_Nodes_InnerJoin o
-
visit_edge o, "left"
-
visit_edge o, "right"
-
end
-
1
alias :visit_Arel_Nodes_OuterJoin :visit_Arel_Nodes_InnerJoin
-
-
1
def visit_Arel_Nodes_DeleteStatement o
-
visit_edge o, "relation"
-
visit_edge o, "wheres"
-
end
-
-
1
def unary o
-
visit_edge o, "expr"
-
end
-
1
alias :visit_Arel_Nodes_Group :unary
-
1
alias :visit_Arel_Nodes_BindParam :unary
-
1
alias :visit_Arel_Nodes_Grouping :unary
-
1
alias :visit_Arel_Nodes_Having :unary
-
1
alias :visit_Arel_Nodes_Limit :unary
-
1
alias :visit_Arel_Nodes_Not :unary
-
1
alias :visit_Arel_Nodes_Offset :unary
-
1
alias :visit_Arel_Nodes_On :unary
-
1
alias :visit_Arel_Nodes_Top :unary
-
1
alias :visit_Arel_Nodes_UnqualifiedColumn :unary
-
-
1
def function o
-
visit_edge o, "expressions"
-
visit_edge o, "distinct"
-
visit_edge o, "alias"
-
end
-
1
alias :visit_Arel_Nodes_Exists :function
-
1
alias :visit_Arel_Nodes_Min :function
-
1
alias :visit_Arel_Nodes_Max :function
-
1
alias :visit_Arel_Nodes_Avg :function
-
1
alias :visit_Arel_Nodes_Sum :function
-
-
1
def visit_Arel_Nodes_NamedFunction o
-
visit_edge o, "name"
-
visit_edge o, "expressions"
-
visit_edge o, "distinct"
-
visit_edge o, "alias"
-
end
-
-
1
def visit_Arel_Nodes_InsertStatement o
-
visit_edge o, "relation"
-
visit_edge o, "columns"
-
visit_edge o, "values"
-
end
-
-
1
def visit_Arel_Nodes_SelectCore o
-
visit_edge o, "source"
-
visit_edge o, "projections"
-
visit_edge o, "wheres"
-
end
-
-
1
def visit_Arel_Nodes_SelectStatement o
-
visit_edge o, "cores"
-
visit_edge o, "limit"
-
visit_edge o, "orders"
-
visit_edge o, "offset"
-
end
-
-
1
def visit_Arel_Nodes_UpdateStatement o
-
visit_edge o, "relation"
-
visit_edge o, "wheres"
-
visit_edge o, "values"
-
end
-
-
1
def visit_Arel_Table o
-
visit_edge o, "name"
-
end
-
-
1
def visit_Arel_Attribute o
-
visit_edge o, "relation"
-
visit_edge o, "name"
-
end
-
1
alias :visit_Arel_Attributes_Integer :visit_Arel_Attribute
-
1
alias :visit_Arel_Attributes_Float :visit_Arel_Attribute
-
1
alias :visit_Arel_Attributes_String :visit_Arel_Attribute
-
1
alias :visit_Arel_Attributes_Time :visit_Arel_Attribute
-
1
alias :visit_Arel_Attributes_Boolean :visit_Arel_Attribute
-
1
alias :visit_Arel_Attributes_Attribute :visit_Arel_Attribute
-
-
1
def nary o
-
o.children.each_with_index do |x,i|
-
edge(i) { visit x }
-
end
-
end
-
1
alias :visit_Arel_Nodes_And :nary
-
-
1
def binary o
-
visit_edge o, "left"
-
visit_edge o, "right"
-
end
-
1
alias :visit_Arel_Nodes_As :binary
-
1
alias :visit_Arel_Nodes_Assignment :binary
-
1
alias :visit_Arel_Nodes_Between :binary
-
1
alias :visit_Arel_Nodes_DoesNotMatch :binary
-
1
alias :visit_Arel_Nodes_Equality :binary
-
1
alias :visit_Arel_Nodes_GreaterThan :binary
-
1
alias :visit_Arel_Nodes_GreaterThanOrEqual :binary
-
1
alias :visit_Arel_Nodes_In :binary
-
1
alias :visit_Arel_Nodes_JoinSource :binary
-
1
alias :visit_Arel_Nodes_LessThan :binary
-
1
alias :visit_Arel_Nodes_LessThanOrEqual :binary
-
1
alias :visit_Arel_Nodes_Matches :binary
-
1
alias :visit_Arel_Nodes_NotEqual :binary
-
1
alias :visit_Arel_Nodes_NotIn :binary
-
1
alias :visit_Arel_Nodes_Or :binary
-
-
1
def visit_String o
-
@node_stack.last.fields << o
-
end
-
1
alias :visit_Time :visit_String
-
1
alias :visit_Date :visit_String
-
1
alias :visit_DateTime :visit_String
-
1
alias :visit_NilClass :visit_String
-
1
alias :visit_TrueClass :visit_String
-
1
alias :visit_FalseClass :visit_String
-
1
alias :visit_Arel_SqlLiteral :visit_String
-
1
alias :visit_Fixnum :visit_String
-
1
alias :visit_BigDecimal :visit_String
-
1
alias :visit_Float :visit_String
-
1
alias :visit_Symbol :visit_String
-
1
alias :visit_Arel_Nodes_SqlLiteral :visit_String
-
-
1
def visit_Hash o
-
o.each_with_index do |pair, i|
-
edge("pair_#{i}") { visit pair }
-
end
-
end
-
-
1
def visit_Array o
-
o.each_with_index do |x,i|
-
edge(i) { visit x }
-
end
-
end
-
-
1
def visit_edge o, method
-
edge(method) { visit o.send(method) }
-
end
-
-
1
def visit o
-
if node = @seen[o.object_id]
-
@edge_stack.last.to = node
-
return
-
end
-
-
node = Node.new(o.class.name, o.object_id)
-
@seen[node.id] = node
-
@nodes << node
-
with_node node do
-
super
-
end
-
end
-
-
1
def edge name
-
edge = Edge.new(name, @node_stack.last)
-
@edge_stack.push edge
-
@edges << edge
-
yield
-
@edge_stack.pop
-
end
-
-
1
def with_node node
-
if edge = @edge_stack.last
-
edge.to = node
-
end
-
-
@node_stack.push node
-
yield
-
@node_stack.pop
-
end
-
-
1
def quote string
-
string.to_s.gsub('"', '\"')
-
end
-
-
1
def to_dot
-
"digraph \"ARel\" {\nnode [width=0.375,height=0.25,shape=record];\n" +
-
@nodes.map { |node|
-
label = "<f0>#{node.name}"
-
-
node.fields.each_with_index do |field, i|
-
label << "|<f#{i + 1}>#{quote field}"
-
end
-
-
"#{node.id} [label=\"#{label}\"];"
-
}.join("\n") + "\n" + @edges.map { |edge|
-
"#{edge.from.id} -> #{edge.to.id} [label=\"#{edge.name}\"];"
-
}.join("\n") + "\n}"
-
end
-
end
-
end
-
end
-
1
module Arel
-
1
module Visitors
-
1
class IBM_DB < Arel::Visitors::ToSql
-
1
private
-
-
1
def visit_Arel_Nodes_Limit o
-
"FETCH FIRST #{visit o.expr} ROWS ONLY"
-
end
-
-
end
-
end
-
end
-
1
module Arel
-
1
module Visitors
-
1
class Informix < Arel::Visitors::ToSql
-
1
private
-
1
def visit_Arel_Nodes_SelectStatement o
-
[
-
"SELECT",
-
(visit(o.offset) if o.offset),
-
(visit(o.limit) if o.limit),
-
o.cores.map { |x| visit_Arel_Nodes_SelectCore x }.join,
-
("ORDER BY #{o.orders.map { |x| visit x }.join(', ')}" unless o.orders.empty?),
-
(visit(o.lock) if o.lock),
-
].compact.join ' '
-
end
-
1
def visit_Arel_Nodes_SelectCore o
-
[
-
"#{o.projections.map { |x| visit x }.join ', '}",
-
("FROM #{visit o.froms}" if o.froms),
-
("WHERE #{o.wheres.map { |x| visit x }.join ' AND ' }" unless o.wheres.empty?),
-
("GROUP BY #{o.groups.map { |x| visit x }.join ', ' }" unless o.groups.empty?),
-
(visit(o.having) if o.having),
-
].compact.join ' '
-
end
-
1
def visit_Arel_Nodes_Offset o
-
"SKIP #{visit o.expr}"
-
end
-
1
def visit_Arel_Nodes_Limit o
-
"LIMIT #{visit o.expr}"
-
end
-
end
-
end
-
end
-
-
1
module Arel
-
1
module Visitors
-
###
-
# This class produces SQL for JOIN clauses but omits the "single-source"
-
# part of the Join grammar:
-
#
-
# http://www.sqlite.org/syntaxdiagrams.html#join-source
-
#
-
# This visitor is used in SelectManager#join_sql and is for backwards
-
# compatibility with Arel V1.0
-
1
module JoinSql
-
1
private
-
-
1
def visit_Arel_Nodes_SelectCore o
-
o.source.right.map { |j| visit j }.join ' '
-
end
-
end
-
end
-
end
-
1
module Arel
-
1
module Visitors
-
1
class MSSQL < Arel::Visitors::ToSql
-
1
private
-
-
# `top` wouldn't really work here. I.e. User.select("distinct first_name").limit(10) would generate
-
# "select top 10 distinct first_name from users", which is invalid query! it should be
-
# "select distinct top 10 first_name from users"
-
1
def visit_Arel_Nodes_Top o
-
""
-
end
-
-
1
def visit_Arel_Nodes_SelectStatement o
-
if !o.limit && !o.offset
-
return super o
-
end
-
-
select_order_by = "ORDER BY #{o.orders.map { |x| visit x }.join(', ')}" unless o.orders.empty?
-
-
is_select_count = false
-
sql = o.cores.map { |x|
-
core_order_by = select_order_by || determine_order_by(x)
-
if select_count? x
-
x.projections = [row_num_literal(core_order_by)]
-
is_select_count = true
-
else
-
x.projections << row_num_literal(core_order_by)
-
end
-
-
visit_Arel_Nodes_SelectCore x
-
}.join
-
-
sql = "SELECT _t.* FROM (#{sql}) as _t WHERE #{get_offset_limit_clause(o)}"
-
# fixme count distinct wouldn't work with limit or offset
-
sql = "SELECT COUNT(1) as count_id FROM (#{sql}) AS subquery" if is_select_count
-
sql
-
end
-
-
1
def get_offset_limit_clause o
-
first_row = o.offset ? o.offset.expr.to_i + 1 : 1
-
last_row = o.limit ? o.limit.expr.to_i - 1 + first_row : nil
-
if last_row
-
" _row_num BETWEEN #{first_row} AND #{last_row}"
-
else
-
" _row_num >= #{first_row}"
-
end
-
end
-
-
1
def determine_order_by x
-
unless x.groups.empty?
-
"ORDER BY #{x.groups.map { |g| visit g }.join ', ' }"
-
else
-
"ORDER BY #{find_left_table_pk(x.froms)}"
-
end
-
end
-
-
1
def row_num_literal order_by
-
Nodes::SqlLiteral.new("ROW_NUMBER() OVER (#{order_by}) as _row_num")
-
end
-
-
1
def select_count? x
-
x.projections.length == 1 && Arel::Nodes::Count === x.projections.first
-
end
-
-
# fixme raise exception of there is no pk?
-
# fixme!! Table.primary_key will be depricated. What is the replacement??
-
1
def find_left_table_pk o
-
return visit o.primary_key if o.instance_of? Arel::Table
-
find_left_table_pk o.left if o.kind_of? Arel::Nodes::Join
-
end
-
end
-
end
-
end
-
1
module Arel
-
1
module Visitors
-
1
class MySQL < Arel::Visitors::ToSql
-
1
private
-
1
def visit_Arel_Nodes_Union o, suppress_parens = false
-
left_result = case o.left
-
when Arel::Nodes::Union
-
visit_Arel_Nodes_Union o.left, true
-
else
-
visit o.left
-
end
-
-
right_result = case o.right
-
when Arel::Nodes::Union
-
visit_Arel_Nodes_Union o.right, true
-
else
-
visit o.right
-
end
-
-
if suppress_parens
-
"#{left_result} UNION #{right_result}"
-
else
-
"( #{left_result} UNION #{right_result} )"
-
end
-
end
-
-
1
def visit_Arel_Nodes_Bin o
-
"BINARY #{visit o.expr}"
-
end
-
-
###
-
# :'(
-
# http://dev.mysql.com/doc/refman/5.0/en/select.html#id3482214
-
1
def visit_Arel_Nodes_SelectStatement o
-
o.limit = Arel::Nodes::Limit.new(18446744073709551615) if o.offset && !o.limit
-
super
-
end
-
-
1
def visit_Arel_Nodes_SelectCore o
-
o.froms ||= Arel.sql('DUAL')
-
super
-
end
-
-
1
def visit_Arel_Nodes_UpdateStatement o
-
[
-
"UPDATE #{visit o.relation}",
-
("SET #{o.values.map { |value| visit value }.join ', '}" unless o.values.empty?),
-
("WHERE #{o.wheres.map { |x| visit x }.join ' AND '}" unless o.wheres.empty?),
-
("ORDER BY #{o.orders.map { |x| visit x }.join(', ')}" unless o.orders.empty?),
-
(visit(o.limit) if o.limit),
-
].compact.join ' '
-
end
-
-
end
-
end
-
end
-
1
module Arel
-
1
module Visitors
-
1
class Oracle < Arel::Visitors::ToSql
-
1
private
-
-
1
def visit_Arel_Nodes_SelectStatement o
-
o = order_hacks(o)
-
-
# if need to select first records without ORDER BY and GROUP BY and without DISTINCT
-
# then can use simple ROWNUM in WHERE clause
-
if o.limit && o.orders.empty? && !o.offset && o.cores.first.projections.first !~ /^DISTINCT /
-
o.cores.last.wheres.push Nodes::LessThanOrEqual.new(
-
Nodes::SqlLiteral.new('ROWNUM'), o.limit.expr
-
)
-
return super
-
end
-
-
if o.limit && o.offset
-
o = o.dup
-
limit = o.limit.expr.to_i
-
offset = o.offset
-
o.offset = nil
-
sql = super(o)
-
return <<-eosql
-
SELECT * FROM (
-
SELECT raw_sql_.*, rownum raw_rnum_
-
FROM (#{sql}) raw_sql_
-
WHERE rownum <= #{offset.expr.to_i + limit}
-
)
-
WHERE #{visit offset}
-
eosql
-
end
-
-
if o.limit
-
o = o.dup
-
limit = o.limit.expr
-
return "SELECT * FROM (#{super(o)}) WHERE ROWNUM <= #{visit limit}"
-
end
-
-
if o.offset
-
o = o.dup
-
offset = o.offset
-
o.offset = nil
-
sql = super(o)
-
return <<-eosql
-
SELECT * FROM (
-
SELECT raw_sql_.*, rownum raw_rnum_
-
FROM (#{sql}) raw_sql_
-
)
-
WHERE #{visit offset}
-
eosql
-
end
-
-
super
-
end
-
-
1
def visit_Arel_Nodes_Limit o
-
end
-
-
1
def visit_Arel_Nodes_Offset o
-
"raw_rnum_ > #{visit o.expr}"
-
end
-
-
1
def visit_Arel_Nodes_Except o
-
"( #{visit o.left} MINUS #{visit o.right} )"
-
end
-
-
1
def visit_Arel_Nodes_UpdateStatement o
-
# Oracle does not allow ORDER BY/LIMIT in UPDATEs.
-
if o.orders.any? && o.limit.nil?
-
# However, there is no harm in silently eating the ORDER BY clause if no LIMIT has been provided,
-
# otherwise let the user deal with the error
-
o = o.dup
-
o.orders = []
-
end
-
-
super
-
end
-
-
###
-
# Hacks for the order clauses specific to Oracle
-
1
def order_hacks o
-
return o if o.orders.empty?
-
return o unless o.cores.any? do |core|
-
core.projections.any? do |projection|
-
/DISTINCT.*FIRST_VALUE/ === projection
-
end
-
end
-
# Previous version with join and split broke ORDER BY clause
-
# if it contained functions with several arguments (separated by ',').
-
#
-
# orders = o.orders.map { |x| visit x }.join(', ').split(',')
-
orders = o.orders.map do |x|
-
string = visit x
-
if string.include?(',')
-
split_order_string(string)
-
else
-
string
-
end
-
end.flatten
-
o.orders = []
-
orders.each_with_index do |order, i|
-
o.orders <<
-
Nodes::SqlLiteral.new("alias_#{i}__#{' DESC' if /\bdesc$/i === order}")
-
end
-
o
-
end
-
-
# Split string by commas but count opening and closing brackets
-
# and ignore commas inside brackets.
-
1
def split_order_string(string)
-
array = []
-
i = 0
-
string.split(',').each do |part|
-
if array[i]
-
array[i] << ',' << part
-
else
-
# to ensure that array[i] will be String and not Arel::Nodes::SqlLiteral
-
array[i] = '' << part
-
end
-
i += 1 if array[i].count('(') == array[i].count(')')
-
end
-
array
-
end
-
-
end
-
end
-
end
-
1
module Arel
-
1
module Visitors
-
1
class OrderClauses < Arel::Visitors::ToSql
-
1
private
-
-
1
def visit_Arel_Nodes_SelectStatement o
-
o.orders.map { |x| visit x }
-
end
-
end
-
end
-
end
-
1
module Arel
-
1
module Visitors
-
1
class PostgreSQL < Arel::Visitors::ToSql
-
1
private
-
-
1
def visit_Arel_Nodes_Matches o
-
"#{visit o.left} ILIKE #{visit o.right}"
-
end
-
-
1
def visit_Arel_Nodes_DoesNotMatch o
-
"#{visit o.left} NOT ILIKE #{visit o.right}"
-
end
-
-
1
def visit_Arel_Nodes_DistinctOn o
-
"DISTINCT ON ( #{visit o.expr} )"
-
end
-
end
-
end
-
end
-
1
module Arel
-
1
module Visitors
-
1
class SQLite < Arel::Visitors::ToSql
-
1
private
-
-
# Locks are not supported in SQLite
-
1
def visit_Arel_Nodes_Lock o
-
end
-
-
1
def visit_Arel_Nodes_SelectStatement o
-
o.limit = Arel::Nodes::Limit.new(-1) if o.offset && !o.limit
-
super
-
end
-
end
-
end
-
end
-
1
require 'bigdecimal'
-
1
require 'date'
-
-
1
module Arel
-
1
module Visitors
-
1
class ToSql < Arel::Visitors::Visitor
-
1
def initialize pool
-
@pool = pool
-
@connection = nil
-
@quoted_tables = {}
-
@quoted_columns = {}
-
end
-
-
1
def accept object
-
self.last_column = nil
-
@pool.with_connection do |conn|
-
@connection = conn
-
super
-
end
-
end
-
-
1
private
-
1
def last_column= col
-
Thread.current[:arel_visitors_to_sql_last_column] = col
-
end
-
-
1
def last_column
-
Thread.current[:arel_visitors_to_sql_last_column]
-
end
-
-
1
def visit_Arel_Nodes_DeleteStatement o
-
[
-
"DELETE FROM #{visit o.relation}",
-
("WHERE #{o.wheres.map { |x| visit x }.join ' AND '}" unless o.wheres.empty?)
-
].compact.join ' '
-
end
-
-
# FIXME: we should probably have a 2-pass visitor for this
-
1
def build_subselect key, o
-
stmt = Nodes::SelectStatement.new
-
core = stmt.cores.first
-
core.froms = o.relation
-
core.wheres = o.wheres
-
core.projections = [key]
-
stmt.limit = o.limit
-
stmt.orders = o.orders
-
stmt
-
end
-
-
1
def visit_Arel_Nodes_UpdateStatement o
-
if o.orders.empty? && o.limit.nil?
-
wheres = o.wheres
-
else
-
key = o.key
-
unless key
-
warn(<<-eowarn) if $VERBOSE
-
(#{caller.first}) Using UpdateManager without setting UpdateManager#key is
-
deprecated and support will be removed in ARel 3.0.0. Please set the primary
-
key on UpdateManager using UpdateManager#key=
-
eowarn
-
key = o.relation.primary_key
-
end
-
-
wheres = [Nodes::In.new(key, [build_subselect(key, o)])]
-
end
-
-
[
-
"UPDATE #{visit o.relation}",
-
("SET #{o.values.map { |value| visit value }.join ', '}" unless o.values.empty?),
-
("WHERE #{wheres.map { |x| visit x }.join ' AND '}" unless wheres.empty?),
-
].compact.join ' '
-
end
-
-
1
def visit_Arel_Nodes_InsertStatement o
-
[
-
"INSERT INTO #{visit o.relation}",
-
-
("(#{o.columns.map { |x|
-
quote_column_name x.name
-
}.join ', '})" unless o.columns.empty?),
-
-
(visit o.values if o.values),
-
].compact.join ' '
-
end
-
-
1
def visit_Arel_Nodes_Exists o
-
"EXISTS (#{visit o.expressions})#{
-
o.alias ? " AS #{visit o.alias}" : ''}"
-
end
-
-
1
def visit_Arel_Nodes_True o
-
"TRUE"
-
end
-
-
1
def visit_Arel_Nodes_False o
-
"FALSE"
-
end
-
-
1
def table_exists? name
-
@pool.table_exists? name
-
end
-
-
1
def column_for attr
-
name = attr.name.to_s
-
table = attr.relation.table_name
-
-
return nil unless table_exists? table
-
-
column_cache[table][name]
-
end
-
-
1
def column_cache
-
@pool.columns_hash
-
end
-
-
1
def visit_Arel_Nodes_Values o
-
"VALUES (#{o.expressions.zip(o.columns).map { |value, attr|
-
if Nodes::SqlLiteral === value
-
visit value
-
else
-
quote(value, attr && column_for(attr))
-
end
-
}.join ', '})"
-
end
-
-
1
def visit_Arel_Nodes_SelectStatement o
-
[
-
(visit(o.with) if o.with),
-
o.cores.map { |x| visit_Arel_Nodes_SelectCore x }.join,
-
("ORDER BY #{o.orders.map { |x| visit x }.join(', ')}" unless o.orders.empty?),
-
(visit(o.limit) if o.limit),
-
(visit(o.offset) if o.offset),
-
(visit(o.lock) if o.lock),
-
].compact.join ' '
-
end
-
-
1
def visit_Arel_Nodes_SelectCore o
-
[
-
"SELECT",
-
(visit(o.top) if o.top),
-
(visit(o.set_quantifier) if o.set_quantifier),
-
("#{o.projections.map { |x| visit x }.join ', '}" unless o.projections.empty?),
-
("FROM #{visit(o.source)}" if o.source && !o.source.empty?),
-
("WHERE #{o.wheres.map { |x| visit x }.join ' AND ' }" unless o.wheres.empty?),
-
("GROUP BY #{o.groups.map { |x| visit x }.join ', ' }" unless o.groups.empty?),
-
(visit(o.having) if o.having),
-
].compact.join ' '
-
end
-
-
1
def visit_Arel_Nodes_Bin o
-
visit o.expr
-
end
-
-
1
def visit_Arel_Nodes_Distinct o
-
'DISTINCT'
-
end
-
-
1
def visit_Arel_Nodes_DistinctOn o
-
raise NotImplementedError, 'DISTINCT ON not implemented for this db'
-
end
-
-
1
def visit_Arel_Nodes_With o
-
"WITH #{o.children.map { |x| visit x }.join(', ')}"
-
end
-
-
1
def visit_Arel_Nodes_WithRecursive o
-
"WITH RECURSIVE #{o.children.map { |x| visit x }.join(', ')}"
-
end
-
-
1
def visit_Arel_Nodes_Union o
-
"( #{visit o.left} UNION #{visit o.right} )"
-
end
-
-
1
def visit_Arel_Nodes_UnionAll o
-
"( #{visit o.left} UNION ALL #{visit o.right} )"
-
end
-
-
1
def visit_Arel_Nodes_Intersect o
-
"( #{visit o.left} INTERSECT #{visit o.right} )"
-
end
-
-
1
def visit_Arel_Nodes_Except o
-
"( #{visit o.left} EXCEPT #{visit o.right} )"
-
end
-
-
1
def visit_Arel_Nodes_Having o
-
"HAVING #{visit o.expr}"
-
end
-
-
1
def visit_Arel_Nodes_Offset o
-
"OFFSET #{visit o.expr}"
-
end
-
-
1
def visit_Arel_Nodes_Limit o
-
"LIMIT #{visit o.expr}"
-
end
-
-
# FIXME: this does nothing on most databases, but does on MSSQL
-
1
def visit_Arel_Nodes_Top o
-
""
-
end
-
-
1
def visit_Arel_Nodes_Lock o
-
visit o.expr
-
end
-
-
1
def visit_Arel_Nodes_Grouping o
-
"(#{visit o.expr})"
-
end
-
-
1
def visit_Arel_Nodes_Ascending o
-
"#{visit o.expr} ASC"
-
end
-
-
1
def visit_Arel_Nodes_Descending o
-
"#{visit o.expr} DESC"
-
end
-
-
1
def visit_Arel_Nodes_Group o
-
visit o.expr
-
end
-
-
1
def visit_Arel_Nodes_NamedFunction o
-
"#{o.name}(#{o.distinct ? 'DISTINCT ' : ''}#{o.expressions.map { |x|
-
visit x
-
}.join(', ')})#{o.alias ? " AS #{visit o.alias}" : ''}"
-
end
-
-
1
def visit_Arel_Nodes_Count o
-
"COUNT(#{o.distinct ? 'DISTINCT ' : ''}#{o.expressions.map { |x|
-
visit x
-
}.join(', ')})#{o.alias ? " AS #{visit o.alias}" : ''}"
-
end
-
-
1
def visit_Arel_Nodes_Sum o
-
"SUM(#{o.expressions.map { |x|
-
visit x }.join(', ')})#{o.alias ? " AS #{visit o.alias}" : ''}"
-
end
-
-
1
def visit_Arel_Nodes_Max o
-
"MAX(#{o.expressions.map { |x|
-
visit x }.join(', ')})#{o.alias ? " AS #{visit o.alias}" : ''}"
-
end
-
-
1
def visit_Arel_Nodes_Min o
-
"MIN(#{o.expressions.map { |x|
-
visit x }.join(', ')})#{o.alias ? " AS #{visit o.alias}" : ''}"
-
end
-
-
1
def visit_Arel_Nodes_Avg o
-
"AVG(#{o.expressions.map { |x|
-
visit x }.join(', ')})#{o.alias ? " AS #{visit o.alias}" : ''}"
-
end
-
-
1
def visit_Arel_Nodes_TableAlias o
-
"#{visit o.relation} #{quote_table_name o.name}"
-
end
-
-
1
def visit_Arel_Nodes_Between o
-
"#{visit o.left} BETWEEN #{visit o.right}"
-
end
-
-
1
def visit_Arel_Nodes_GreaterThanOrEqual o
-
"#{visit o.left} >= #{visit o.right}"
-
end
-
-
1
def visit_Arel_Nodes_GreaterThan o
-
"#{visit o.left} > #{visit o.right}"
-
end
-
-
1
def visit_Arel_Nodes_LessThanOrEqual o
-
"#{visit o.left} <= #{visit o.right}"
-
end
-
-
1
def visit_Arel_Nodes_LessThan o
-
"#{visit o.left} < #{visit o.right}"
-
end
-
-
1
def visit_Arel_Nodes_Matches o
-
"#{visit o.left} LIKE #{visit o.right}"
-
end
-
-
1
def visit_Arel_Nodes_DoesNotMatch o
-
"#{visit o.left} NOT LIKE #{visit o.right}"
-
end
-
-
1
def visit_Arel_Nodes_JoinSource o
-
[
-
(visit(o.left) if o.left),
-
o.right.map { |j| visit j }.join(' ')
-
].compact.join ' '
-
end
-
-
1
def visit_Arel_Nodes_StringJoin o
-
visit o.left
-
end
-
-
1
def visit_Arel_Nodes_OuterJoin o
-
"LEFT OUTER JOIN #{visit o.left} #{visit o.right}"
-
end
-
-
1
def visit_Arel_Nodes_InnerJoin o
-
"INNER JOIN #{visit o.left} #{visit o.right if o.right}"
-
end
-
-
1
def visit_Arel_Nodes_On o
-
"ON #{visit o.expr}"
-
end
-
-
1
def visit_Arel_Nodes_Not o
-
"NOT (#{visit o.expr})"
-
end
-
-
1
def visit_Arel_Table o
-
if o.table_alias
-
"#{quote_table_name o.name} #{quote_table_name o.table_alias}"
-
else
-
quote_table_name o.name
-
end
-
end
-
-
1
def visit_Arel_Nodes_In o
-
"#{visit o.left} IN (#{visit o.right})"
-
end
-
-
1
def visit_Arel_Nodes_NotIn o
-
"#{visit o.left} NOT IN (#{visit o.right})"
-
end
-
-
1
def visit_Arel_Nodes_And o
-
o.children.map { |x| visit x }.join ' AND '
-
end
-
-
1
def visit_Arel_Nodes_Or o
-
"#{visit o.left} OR #{visit o.right}"
-
end
-
-
1
def visit_Arel_Nodes_Assignment o
-
right = quote(o.right, column_for(o.left))
-
"#{visit o.left} = #{right}"
-
end
-
-
1
def visit_Arel_Nodes_Equality o
-
right = o.right
-
-
if right.nil?
-
"#{visit o.left} IS NULL"
-
else
-
"#{visit o.left} = #{visit right}"
-
end
-
end
-
-
1
def visit_Arel_Nodes_NotEqual o
-
right = o.right
-
-
if right.nil?
-
"#{visit o.left} IS NOT NULL"
-
else
-
"#{visit o.left} != #{visit right}"
-
end
-
end
-
-
1
def visit_Arel_Nodes_As o
-
"#{visit o.left} AS #{visit o.right}"
-
end
-
-
1
def visit_Arel_Nodes_UnqualifiedColumn o
-
"#{quote_column_name o.name}"
-
end
-
-
1
def visit_Arel_Attributes_Attribute o
-
self.last_column = column_for o
-
join_name = o.relation.table_alias || o.relation.name
-
"#{quote_table_name join_name}.#{quote_column_name o.name}"
-
end
-
1
alias :visit_Arel_Attributes_Integer :visit_Arel_Attributes_Attribute
-
1
alias :visit_Arel_Attributes_Float :visit_Arel_Attributes_Attribute
-
1
alias :visit_Arel_Attributes_Decimal :visit_Arel_Attributes_Attribute
-
1
alias :visit_Arel_Attributes_String :visit_Arel_Attributes_Attribute
-
1
alias :visit_Arel_Attributes_Time :visit_Arel_Attributes_Attribute
-
1
alias :visit_Arel_Attributes_Boolean :visit_Arel_Attributes_Attribute
-
-
1
def literal o; o end
-
-
1
alias :visit_Arel_Nodes_BindParam :literal
-
1
alias :visit_Arel_Nodes_SqlLiteral :literal
-
1
alias :visit_Arel_SqlLiteral :literal # This is deprecated
-
1
alias :visit_Bignum :literal
-
1
alias :visit_Fixnum :literal
-
-
1
def quoted o; quote(o, last_column) end
-
-
1
alias :visit_ActiveSupport_Multibyte_Chars :quoted
-
1
alias :visit_ActiveSupport_StringInquirer :quoted
-
1
alias :visit_BigDecimal :quoted
-
1
alias :visit_Class :quoted
-
1
alias :visit_Date :quoted
-
1
alias :visit_DateTime :quoted
-
1
alias :visit_FalseClass :quoted
-
1
alias :visit_Float :quoted
-
1
alias :visit_Hash :quoted
-
1
alias :visit_NilClass :quoted
-
1
alias :visit_String :quoted
-
1
alias :visit_Symbol :quoted
-
1
alias :visit_Time :quoted
-
1
alias :visit_TrueClass :quoted
-
-
1
def visit_Arel_Nodes_InfixOperation o
-
"#{visit o.left} #{o.operator} #{visit o.right}"
-
end
-
-
1
alias :visit_Arel_Nodes_Addition :visit_Arel_Nodes_InfixOperation
-
1
alias :visit_Arel_Nodes_Subtraction :visit_Arel_Nodes_InfixOperation
-
1
alias :visit_Arel_Nodes_Multiplication :visit_Arel_Nodes_InfixOperation
-
1
alias :visit_Arel_Nodes_Division :visit_Arel_Nodes_InfixOperation
-
-
1
def visit_Array o
-
o.empty? ? 'NULL' : o.map { |x| visit x }.join(', ')
-
end
-
-
1
def quote value, column = nil
-
@connection.quote value, column
-
end
-
-
1
def quote_table_name name
-
return name if Arel::Nodes::SqlLiteral === name
-
@quoted_tables[name] ||= @connection.quote_table_name(name)
-
end
-
-
1
def quote_column_name name
-
@quoted_columns[name] ||= Arel::Nodes::SqlLiteral === name ? name : @connection.quote_column_name(name)
-
end
-
end
-
end
-
end
-
1
module Arel
-
1
module Visitors
-
1
class Visitor
-
1
def accept object
-
visit object
-
end
-
-
1
private
-
-
1
DISPATCH = Hash.new do |hash, klass|
-
hash[klass] = "visit_#{(klass.name || '').gsub('::', '_')}"
-
end
-
-
1
def dispatch
-
DISPATCH
-
end
-
-
1
def visit object
-
send dispatch[object.class], object
-
rescue NoMethodError => e
-
raise e if respond_to?(dispatch[object.class], true)
-
superklass = object.class.ancestors.find { |klass|
-
respond_to?(dispatch[klass], true)
-
}
-
raise(TypeError, "Cannot visit #{object.class}") unless superklass
-
dispatch[object.class] = dispatch[superklass]
-
retry
-
end
-
end
-
end
-
end
-
1
module Arel
-
1
module Visitors
-
1
class WhereSql < Arel::Visitors::ToSql
-
1
def visit_Arel_Nodes_SelectCore o
-
"WHERE #{o.wheres.map { |x| visit x }.join ' AND ' }"
-
end
-
end
-
end
-
end
-
1
require 'coffee-script'
-
require 'coffee/rails/railtie'
-
require 'coffee/rails/template_handler'
-
require 'coffee/rails/version'
-
1
require 'coffee_script'
-
1
require 'execjs'
-
require 'coffee_script/source'
-
-
module CoffeeScript
-
EngineError = ExecJS::RuntimeError
-
CompilationError = ExecJS::ProgramError
-
-
module Source
-
def self.path
-
@path ||= ENV['COFFEESCRIPT_SOURCE_PATH'] || bundled_path
-
end
-
-
def self.path=(path)
-
@contents = @version = @bare_option = @context = nil
-
@path = path
-
end
-
-
def self.contents
-
@contents ||= File.read(path)
-
end
-
-
def self.version
-
@version ||= contents[/CoffeeScript Compiler v([\d.]+)/, 1]
-
end
-
-
def self.bare_option
-
@bare_option ||= contents.match(/noWrap/) ? 'noWrap' : 'bare'
-
end
-
-
def self.context
-
@context ||= ExecJS.compile(contents)
-
end
-
end
-
-
class << self
-
def engine
-
end
-
-
def engine=(engine)
-
end
-
-
def version
-
Source.version
-
end
-
-
# Compile a script (String or IO) to JavaScript.
-
def compile(script, options = {})
-
script = script.read if script.respond_to?(:read)
-
-
if options.key?(:bare)
-
elsif options.key?(:no_wrap)
-
options[:bare] = options[:no_wrap]
-
else
-
options[:bare] = false
-
end
-
-
Source.context.call("CoffeeScript.compile", script, options)
-
end
-
end
-
end
-
1
require "execjs/module"
-
1
require "execjs/runtimes"
-
-
1
module ExecJS
-
1
self.runtime ||= Runtimes.autodetect
-
end
-
1
module ExecJS
-
1
class DisabledRuntime
-
1
def name
-
"Disabled"
-
end
-
-
1
def exec(source)
-
raise Error, "ExecJS disabled"
-
end
-
-
1
def eval(source)
-
raise Error, "ExecJS disabled"
-
end
-
-
1
def compile(source)
-
raise Error, "ExecJS disabled"
-
end
-
-
1
def available?
-
true
-
end
-
end
-
end
-
1
require "shellwords"
-
1
require "tempfile"
-
-
1
module ExecJS
-
1
class ExternalRuntime
-
1
class Context
-
1
def initialize(runtime, source = "")
-
source = source.encode('UTF-8') if source.respond_to?(:encode)
-
-
@runtime = runtime
-
@source = source
-
end
-
-
1
def eval(source, options = {})
-
source = source.encode('UTF-8') if source.respond_to?(:encode)
-
-
if /\S/ =~ source
-
exec("return eval(#{MultiJson.encode("(#{source})")})")
-
end
-
end
-
-
1
def exec(source, options = {})
-
source = source.encode('UTF-8') if source.respond_to?(:encode)
-
source = "#{@source}\n#{source}" if @source
-
-
compile_to_tempfile(source) do |file|
-
extract_result(@runtime.send(:exec_runtime, file.path))
-
end
-
end
-
-
1
def call(identifier, *args)
-
eval "#{identifier}.apply(this, #{MultiJson.encode(args)})"
-
end
-
-
1
protected
-
1
def compile_to_tempfile(source)
-
tempfile = Tempfile.open(['execjs', '.js'])
-
tempfile.write compile(source)
-
tempfile.close
-
yield tempfile
-
ensure
-
tempfile.close!
-
end
-
-
1
def compile(source)
-
@runtime.send(:runner_source).dup.tap do |output|
-
output.sub!('#{source}') do
-
source
-
end
-
output.sub!('#{encoded_source}') do
-
encoded_source = encode_unicode_codepoints(source)
-
MultiJson.encode("(function(){ #{encoded_source} })()")
-
end
-
output.sub!('#{json2_source}') do
-
IO.read(ExecJS.root + "/support/json2.js")
-
end
-
end
-
end
-
-
1
def extract_result(output)
-
status, value = output.empty? ? [] : MultiJson.decode(output)
-
if status == "ok"
-
value
-
elsif value =~ /SyntaxError:/
-
raise RuntimeError, value
-
else
-
raise ProgramError, value
-
end
-
end
-
-
1
if "".respond_to?(:codepoints)
-
1
def encode_unicode_codepoints(str)
-
str.gsub(/[\u0080-\uffff]/) do |ch|
-
"\\u%04x" % ch.codepoints.to_a
-
end
-
end
-
else
-
def encode_unicode_codepoints(str)
-
str.gsub(/([\xC0-\xDF][\x80-\xBF]|
-
[\xE0-\xEF][\x80-\xBF]{2}|
-
[\xF0-\xF7][\x80-\xBF]{3})+/nx) do |ch|
-
"\\u%04x" % ch.unpack("U*")
-
end
-
end
-
end
-
end
-
-
1
attr_reader :name
-
-
1
def initialize(options)
-
4
@name = options[:name]
-
4
@command = options[:command]
-
4
@runner_path = options[:runner_path]
-
4
@test_args = options[:test_args]
-
4
@test_match = options[:test_match]
-
4
@encoding = options[:encoding]
-
4
@binary = nil
-
end
-
-
1
def exec(source)
-
context = Context.new(self)
-
context.exec(source)
-
end
-
-
1
def eval(source)
-
context = Context.new(self)
-
context.eval(source)
-
end
-
-
1
def compile(source)
-
Context.new(self, source)
-
end
-
-
1
def available?
-
4
require "multi_json"
-
4
binary ? true : false
-
end
-
-
1
private
-
1
def binary
-
4
@binary ||= locate_binary
-
end
-
-
1
def locate_executable(cmd)
-
5
if ExecJS.windows? && File.extname(cmd) == ""
-
cmd << ".exe"
-
end
-
-
5
if File.executable? cmd
-
cmd
-
else
-
5
path = ENV['PATH'].split(File::PATH_SEPARATOR).find { |p|
-
45
full_path = File.join(p, cmd)
-
45
File.executable?(full_path) && File.file?(full_path)
-
}
-
5
path && File.expand_path(cmd, path)
-
end
-
end
-
-
1
protected
-
1
def runner_source
-
@runner_source ||= IO.read(@runner_path)
-
end
-
-
1
def exec_runtime(filename)
-
output = sh("#{shell_escape(*(binary.split(' ') << filename))} 2>&1")
-
if $?.success?
-
output
-
else
-
raise RuntimeError, output
-
end
-
end
-
-
1
def locate_binary
-
4
if binary = which(@command)
-
if @test_args
-
output = `#{shell_escape(binary, @test_args)} 2>&1`
-
binary if output.match(@test_match)
-
else
-
binary
-
end
-
end
-
end
-
-
1
def which(command)
-
4
Array(command).find do |name|
-
5
name, args = name.split(/\s+/, 2)
-
5
path = locate_executable(name)
-
-
5
next unless path
-
-
args ? "#{path} #{args}" : path
-
end
-
end
-
-
1
if "".respond_to?(:force_encoding)
-
1
def sh(command)
-
output, options = nil, {}
-
options[:external_encoding] = @encoding if @encoding
-
options[:internal_encoding] = Encoding.default_internal || 'UTF-8'
-
IO.popen(command, options) { |f| output = f.read }
-
output
-
end
-
else
-
require "iconv"
-
-
def sh(command)
-
output = nil
-
IO.popen(command) { |f| output = f.read }
-
-
if @encoding
-
Iconv.new('UTF-8', @encoding).iconv(output)
-
else
-
output
-
end
-
end
-
end
-
-
1
if ExecJS.windows?
-
def shell_escape(*args)
-
# see http://technet.microsoft.com/en-us/library/cc723564.aspx#XSLTsection123121120120
-
args.map { |arg|
-
arg = %Q("#{arg.gsub('"','""')}") if arg.match(/[&|()<>^ "]/)
-
arg
-
}.join(" ")
-
end
-
else
-
1
def shell_escape(*args)
-
Shellwords.join(args)
-
end
-
end
-
end
-
end
-
1
module ExecJS
-
1
class JohnsonRuntime
-
1
class Context
-
1
def initialize(source = "")
-
@runtime = Johnson::Runtime.new
-
@runtime.evaluate(source)
-
end
-
-
1
def exec(source, options = {})
-
source = source.encode('UTF-8') if source.respond_to?(:encode)
-
-
if /\S/ =~ source
-
eval "(function(){#{source}})()", options
-
end
-
end
-
-
1
def eval(source, options = {})
-
source = source.encode('UTF-8') if source.respond_to?(:encode)
-
-
if /\S/ =~ source
-
unbox @runtime.evaluate("(#{source})")
-
end
-
rescue Johnson::Error => e
-
if syntax_error?(e)
-
raise RuntimeError, e.message
-
else
-
raise ProgramError, e.message
-
end
-
end
-
-
1
def call(properties, *args)
-
unbox @runtime.evaluate(properties).call(*args)
-
rescue Johnson::Error => e
-
if syntax_error?(e)
-
raise RuntimeError, e.message
-
else
-
raise ProgramError, e.message
-
end
-
end
-
-
1
def unbox(value)
-
case
-
when function?(value)
-
nil
-
when string?(value)
-
value.respond_to?(:force_encoding) ?
-
value.force_encoding('UTF-8') :
-
value
-
when array?(value)
-
value.map { |v| unbox(v) }
-
when object?(value)
-
value.inject({}) do |vs, (k, v)|
-
vs[k] = unbox(v) unless function?(v)
-
vs
-
end
-
else
-
value
-
end
-
end
-
-
1
private
-
1
def syntax_error?(error)
-
error.message =~ /^syntax error at /
-
end
-
-
1
def function?(value)
-
value.respond_to?(:function?) && value.function?
-
end
-
-
1
def string?(value)
-
value.is_a?(String)
-
end
-
-
1
def array?(value)
-
array_test.call(value)
-
end
-
-
1
def object?(value)
-
value.respond_to?(:inject)
-
end
-
-
1
def array_test
-
@array_test ||= @runtime.evaluate("(function(a) {return a instanceof [].constructor})")
-
end
-
end
-
-
1
def name
-
"Johnson (SpiderMonkey)"
-
end
-
-
1
def exec(source)
-
context = Context.new
-
context.exec(source)
-
end
-
-
1
def eval(source)
-
context = Context.new
-
context.eval(source)
-
end
-
-
1
def compile(source)
-
Context.new(source)
-
end
-
-
1
def available?
-
1
require "johnson"
-
true
-
rescue LoadError
-
1
false
-
end
-
end
-
end
-
1
require "execjs/version"
-
1
require "rbconfig"
-
-
1
module ExecJS
-
1
class Error < ::StandardError; end
-
1
class RuntimeError < Error; end
-
1
class ProgramError < Error; end
-
1
class RuntimeUnavailable < RuntimeError; end
-
-
1
class << self
-
1
attr_reader :runtime
-
-
1
def runtime=(runtime)
-
raise RuntimeUnavailable, "#{runtime.name} is unavailable on this system" unless runtime.available?
-
@runtime = runtime
-
end
-
-
1
def exec(source)
-
runtime.exec(source)
-
end
-
-
1
def eval(source)
-
runtime.eval(source)
-
end
-
-
1
def compile(source)
-
runtime.compile(source)
-
end
-
-
1
def root
-
4
@root ||= File.expand_path("..", __FILE__)
-
end
-
-
1
def windows?
-
6
@windows ||= RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
-
end
-
end
-
end
-
1
module ExecJS
-
1
class MustangRuntime
-
1
class Context
-
1
def initialize(source = "")
-
source = source.encode('UTF-8') if source.respond_to?(:encode)
-
-
@v8_context = ::Mustang::Context.new
-
@v8_context.eval(source)
-
end
-
-
1
def exec(source, options = {})
-
source = source.encode('UTF-8') if source.respond_to?(:encode)
-
-
if /\S/ =~ source
-
eval "(function(){#{source}})()", options
-
end
-
end
-
-
1
def eval(source, options = {})
-
source = source.encode('UTF-8') if source.respond_to?(:encode)
-
-
if /\S/ =~ source
-
unbox @v8_context.eval("(#{source})")
-
end
-
end
-
-
1
def call(properties, *args)
-
unbox @v8_context.eval(properties).call(*args)
-
rescue NoMethodError => e
-
raise ProgramError, e.message
-
end
-
-
1
def unbox(value)
-
case value
-
when Mustang::V8::Array
-
value.map { |v| unbox(v) }
-
when Mustang::V8::Boolean
-
value.to_bool
-
when Mustang::V8::NullClass, Mustang::V8::UndefinedClass
-
nil
-
when Mustang::V8::Function
-
nil
-
when Mustang::V8::SyntaxError
-
raise RuntimeError, value.message
-
when Mustang::V8::Error
-
raise ProgramError, value.message
-
when Mustang::V8::Object
-
value.inject({}) { |h, (k, v)|
-
v = unbox(v)
-
h[k] = v if v
-
h
-
}
-
else
-
value.respond_to?(:delegate) ? value.delegate : value
-
end
-
end
-
end
-
-
1
def name
-
"Mustang (V8)"
-
end
-
-
1
def exec(source)
-
context = Context.new
-
context.exec(source)
-
end
-
-
1
def eval(source)
-
context = Context.new
-
context.eval(source)
-
end
-
-
1
def compile(source)
-
Context.new(source)
-
end
-
-
1
def available?
-
1
require "mustang"
-
true
-
rescue LoadError
-
1
false
-
end
-
end
-
end
-
1
module ExecJS
-
1
class RubyRacerRuntime
-
1
class Context
-
1
def initialize(source = "")
-
source = source.encode('UTF-8') if source.respond_to?(:encode)
-
-
lock do
-
@v8_context = ::V8::Context.new
-
@v8_context.eval(source)
-
end
-
end
-
-
1
def exec(source, options = {})
-
source = source.encode('UTF-8') if source.respond_to?(:encode)
-
-
if /\S/ =~ source
-
eval "(function(){#{source}})()", options
-
end
-
end
-
-
1
def eval(source, options = {})
-
source = source.encode('UTF-8') if source.respond_to?(:encode)
-
-
if /\S/ =~ source
-
lock do
-
begin
-
unbox @v8_context.eval("(#{source})")
-
rescue ::V8::JSError => e
-
if e.value["name"] == "SyntaxError"
-
raise RuntimeError, e.message
-
else
-
raise ProgramError, e.message
-
end
-
end
-
end
-
end
-
end
-
-
1
def call(properties, *args)
-
lock do
-
begin
-
unbox @v8_context.eval(properties).call(*args)
-
rescue ::V8::JSError => e
-
if e.value["name"] == "SyntaxError"
-
raise RuntimeError, e.message
-
else
-
raise ProgramError, e.message
-
end
-
end
-
end
-
end
-
-
1
def unbox(value)
-
case value
-
when ::V8::Function
-
nil
-
when ::V8::Array
-
value.map { |v| unbox(v) }
-
when ::V8::Object
-
value.inject({}) do |vs, (k, v)|
-
vs[k] = unbox(v) unless v.is_a?(::V8::Function)
-
vs
-
end
-
when String
-
value.respond_to?(:force_encoding) ?
-
value.force_encoding('UTF-8') :
-
value
-
else
-
value
-
end
-
end
-
-
1
private
-
1
def lock
-
result, exception = nil, nil
-
V8::C::Locker() do
-
begin
-
result = yield
-
rescue Exception => e
-
exception = e
-
end
-
end
-
-
if exception
-
raise exception
-
else
-
result
-
end
-
end
-
end
-
-
1
def name
-
"therubyracer (V8)"
-
end
-
-
1
def exec(source)
-
context = Context.new
-
context.exec(source)
-
end
-
-
1
def eval(source)
-
context = Context.new
-
context.eval(source)
-
end
-
-
1
def compile(source)
-
Context.new(source)
-
end
-
-
1
def available?
-
1
require "v8"
-
true
-
rescue LoadError
-
1
false
-
end
-
end
-
end
-
1
module ExecJS
-
1
class RubyRhinoRuntime
-
1
class Context
-
1
def initialize(source = "")
-
source = source.encode('UTF-8') if source.respond_to?(:encode)
-
-
@rhino_context = ::Rhino::Context.new
-
fix_memory_limit! @rhino_context
-
@rhino_context.eval(source)
-
end
-
-
1
def exec(source, options = {})
-
source = source.encode('UTF-8') if source.respond_to?(:encode)
-
-
if /\S/ =~ source
-
eval "(function(){#{source}})()", options
-
end
-
end
-
-
1
def eval(source, options = {})
-
source = source.encode('UTF-8') if source.respond_to?(:encode)
-
-
if /\S/ =~ source
-
unbox @rhino_context.eval("(#{source})")
-
end
-
rescue ::Rhino::JavascriptError => e
-
if e.message == "syntax error"
-
raise RuntimeError, e.message
-
else
-
raise ProgramError, e.message
-
end
-
end
-
-
1
def call(properties, *args)
-
unbox @rhino_context.eval(properties).call(*args)
-
rescue ::Rhino::JavascriptError => e
-
if e.message == "syntax error"
-
raise RuntimeError, e.message
-
else
-
raise ProgramError, e.message
-
end
-
end
-
-
1
def unbox(value)
-
case value = ::Rhino::To.ruby(value)
-
when ::Rhino::NativeFunction
-
nil
-
when ::Rhino::NativeObject
-
value.inject({}) do |vs, (k, v)|
-
case v
-
when ::Rhino::NativeFunction, ::Rhino::J::Function
-
nil
-
else
-
vs[k] = unbox(v)
-
end
-
vs
-
end
-
when Array
-
value.map { |v| unbox(v) }
-
else
-
value
-
end
-
end
-
-
1
private
-
# Disables bytecode compiling which limits you to 64K scripts
-
1
def fix_memory_limit!(context)
-
if context.respond_to?(:optimization_level=)
-
context.optimization_level = -1
-
else
-
context.instance_eval { @native.setOptimizationLevel(-1) }
-
end
-
end
-
end
-
-
1
def name
-
"therubyrhino (Rhino)"
-
end
-
-
1
def exec(source)
-
context = Context.new
-
context.exec(source)
-
end
-
-
1
def eval(source)
-
context = Context.new
-
context.eval(source)
-
end
-
-
1
def compile(source)
-
Context.new(source)
-
end
-
-
1
def available?
-
1
require "rhino"
-
true
-
rescue LoadError
-
1
false
-
end
-
end
-
end
-
1
require "execjs/module"
-
1
require "execjs/disabled_runtime"
-
1
require "execjs/external_runtime"
-
1
require "execjs/johnson_runtime"
-
1
require "execjs/mustang_runtime"
-
1
require "execjs/ruby_racer_runtime"
-
1
require "execjs/ruby_rhino_runtime"
-
-
1
module ExecJS
-
1
module Runtimes
-
1
Disabled = DisabledRuntime.new
-
-
1
RubyRacer = RubyRacerRuntime.new
-
-
1
RubyRhino = RubyRhinoRuntime.new
-
-
1
Johnson = JohnsonRuntime.new
-
-
1
Mustang = MustangRuntime.new
-
-
1
Node = ExternalRuntime.new(
-
:name => "Node.js (V8)",
-
:command => ["nodejs", "node"],
-
:runner_path => ExecJS.root + "/support/node_runner.js",
-
:encoding => 'UTF-8'
-
)
-
-
1
JavaScriptCore = ExternalRuntime.new(
-
:name => "JavaScriptCore",
-
:command => "/System/Library/Frameworks/JavaScriptCore.framework/Versions/A/Resources/jsc",
-
:runner_path => ExecJS.root + "/support/jsc_runner.js"
-
)
-
-
1
SpiderMonkey = Spidermonkey = ExternalRuntime.new(
-
:name => "SpiderMonkey",
-
:command => "js",
-
:runner_path => ExecJS.root + "/support/spidermonkey_runner.js"
-
)
-
-
1
JScript = ExternalRuntime.new(
-
:name => "JScript",
-
:command => "cscript //E:jscript //Nologo //U",
-
:runner_path => ExecJS.root + "/support/jscript_runner.js",
-
:encoding => 'UTF-16LE' # CScript with //U returns UTF-16LE
-
)
-
-
-
1
def self.autodetect
-
1
from_environment || best_available ||
-
raise(RuntimeUnavailable, "Could not find a JavaScript runtime. " +
-
"See https://github.com/sstephenson/execjs for a list of available runtimes.")
-
end
-
-
1
def self.best_available
-
1
runtimes.find(&:available?)
-
end
-
-
1
def self.from_environment
-
1
if name = ENV["EXECJS_RUNTIME"]
-
if runtime = const_get(name)
-
if runtime.available?
-
runtime if runtime.available?
-
else
-
raise RuntimeUnavailable, "#{runtime.name} runtime is not available on this system"
-
end
-
elsif !name.empty?
-
raise RuntimeUnavailable, "#{name} runtime is not defined"
-
end
-
end
-
end
-
-
1
def self.names
-
@names ||= constants.inject({}) { |h, name| h.merge(const_get(name) => name) }.values
-
end
-
-
1
def self.runtimes
-
@runtimes ||= [
-
RubyRacer,
-
RubyRhino,
-
Johnson,
-
Mustang,
-
Node,
-
JavaScriptCore,
-
SpiderMonkey,
-
JScript
-
1
]
-
end
-
end
-
-
1
def self.runtimes
-
Runtimes.runtimes
-
end
-
end
-
1
module ExecJS
-
1
VERSION = "1.3.0"
-
end
-
# This backports the Ruby 1.9 String interpolation syntax to Ruby 1.8.
-
#
-
# This backport has been shipped with I18n for a number of versions. Meanwhile
-
# Rails has started to rely on it and we are going to move it to ActiveSupport.
-
# See https://rails.lighthouseapp.com/projects/8994/tickets/6013-move-19-string-interpolation-syntax-backport-from-i18n-to-activesupport
-
#
-
# Once the above patch has been applied to Rails the following code will be
-
# removed from I18n.
-
-
=begin
-
heavily based on Masao Mutoh's gettext String interpolation extension
-
http://github.com/mutoh/gettext/blob/f6566738b981fe0952548c421042ad1e0cdfb31e/lib/gettext/core_ext/string.rb
-
Copyright (C) 2005-2009 Masao Mutoh
-
You may redistribute it and/or modify it under the same license terms as Ruby.
-
=end
-
-
1
begin
-
1
raise ArgumentError if ("a %{x}" % {:x=>'b'}) != 'a b'
-
rescue ArgumentError
-
# KeyError is raised by String#% when the string contains a named placeholder
-
# that is not contained in the given arguments hash. Ruby 1.9 includes and
-
# raises this exception natively. We define it to mimic Ruby 1.9's behaviour
-
# in Ruby 1.8.x
-
class KeyError < IndexError
-
def initialize(message = nil)
-
super(message || "key not found")
-
end
-
end unless defined?(KeyError)
-
-
# Extension for String class. This feature is included in Ruby 1.9 or later but not occur TypeError.
-
#
-
# String#% method which accept "named argument". The translator can know
-
# the meaning of the msgids using "named argument" instead of %s/%d style.
-
class String
-
# For older ruby versions, such as ruby-1.8.5
-
alias :bytesize :size unless instance_methods.find {|m| m.to_s == 'bytesize'}
-
alias :interpolate_without_ruby_19_syntax :% # :nodoc:
-
-
INTERPOLATION_PATTERN = Regexp.union(
-
/%\{(\w+)\}/, # matches placeholders like "%{foo}"
-
/%<(\w+)>(.*?\d*\.?\d*[bBdiouxXeEfgGcps])/ # matches placeholders like "%<foo>.d"
-
)
-
-
INTERPOLATION_PATTERN_WITH_ESCAPE = Regexp.union(
-
/%%/,
-
INTERPOLATION_PATTERN
-
)
-
-
# % uses self (i.e. the String) as a format specification and returns the
-
# result of applying it to the given arguments. In other words it interpolates
-
# the given arguments to the string according to the formats the string
-
# defines.
-
#
-
# There are three ways to use it:
-
#
-
# * Using a single argument or Array of arguments.
-
#
-
# This is the default behaviour of the String class. See Kernel#sprintf for
-
# more details about the format string.
-
#
-
# Example:
-
#
-
# "%d %s" % [1, "message"]
-
# # => "1 message"
-
#
-
# * Using a Hash as an argument and unformatted, named placeholders.
-
#
-
# When you pass a Hash as an argument and specify placeholders with %{foo}
-
# it will interpret the hash values as named arguments.
-
#
-
# Example:
-
#
-
# "%{firstname}, %{lastname}" % {:firstname => "Masao", :lastname => "Mutoh"}
-
# # => "Masao Mutoh"
-
#
-
# * Using a Hash as an argument and formatted, named placeholders.
-
#
-
# When you pass a Hash as an argument and specify placeholders with %<foo>d
-
# it will interpret the hash values as named arguments and format the value
-
# according to the formatting instruction appended to the closing >.
-
#
-
# Example:
-
#
-
# "%<integer>d, %<float>.1f" % { :integer => 10, :float => 43.4 }
-
# # => "10, 43.3"
-
def %(args)
-
if args.kind_of?(Hash)
-
dup.gsub(INTERPOLATION_PATTERN_WITH_ESCAPE) do |match|
-
if match == '%%'
-
'%'
-
else
-
key = ($1 || $2).to_sym
-
raise KeyError unless args.has_key?(key)
-
$3 ? sprintf("%#{$3}", args[key]) : args[key]
-
end
-
end
-
elsif self =~ INTERPOLATION_PATTERN
-
raise ArgumentError.new('one hash required')
-
else
-
result = gsub(/%([{<])/, '%%\1')
-
result.send :'interpolate_without_ruby_19_syntax', args
-
end
-
end
-
end
-
end
-
1
require "rails"
-
-
%w(
-
active_record
-
1
action_controller
-
action_mailer
-
active_resource
-
rails/test_unit
-
sprockets
-
).each do |framework|
-
6
begin
-
6
require "#{framework}/railtie"
-
rescue LoadError
-
end
-
end
-
1
module Rails
-
1
class TestUnitRailtie < Rails::Railtie
-
1
config.app_generators do |c|
-
1
c.test_framework :test_unit, :fixture => true,
-
:fixture_replacement => nil
-
-
1
c.integration_tool :test_unit
-
1
c.performance_tool :test_unit
-
end
-
-
1
rake_tasks do
-
load "rails/test_unit/testing.rake"
-
end
-
end
-
end
-
1
dir = File.dirname(__FILE__)
-
1
$LOAD_PATH.unshift dir unless $LOAD_PATH.include?(dir)
-
-
# This is necessary to set so that the Haml code that tries to load Sass
-
# knows that Sass is indeed loading,
-
# even if there's some crazy autoload stuff going on.
-
1
SASS_BEGUN_TO_LOAD = true unless defined?(SASS_BEGUN_TO_LOAD)
-
-
1
require 'sass/version'
-
-
# The module that contains everything Sass-related:
-
#
-
# * {Sass::Engine} is the class used to render Sass/SCSS within Ruby code.
-
# * {Sass::Plugin} is interfaces with web frameworks (Rails and Merb in particular).
-
# * {Sass::SyntaxError} is raised when Sass encounters an error.
-
# * {Sass::CSS} handles conversion of CSS to Sass.
-
#
-
# Also see the {file:SASS_REFERENCE.md full Sass reference}.
-
1
module Sass
-
# Compile a Sass or SCSS string to CSS.
-
# Defaults to SCSS.
-
#
-
# @param contents [String] The contents of the Sass file.
-
# @param options [{Symbol => Object}] An options hash;
-
# see {file:SASS_REFERENCE.md#sass_options the Sass options documentation}
-
# @raise [Sass::SyntaxError] if there's an error in the document
-
# @raise [Encoding::UndefinedConversionError] if the source encoding
-
# cannot be converted to UTF-8
-
# @raise [ArgumentError] if the document uses an unknown encoding with `@charset`
-
1
def self.compile(contents, options = {})
-
options[:syntax] ||= :scss
-
Engine.new(contents, options).to_css
-
end
-
-
# Compile a file on disk to CSS.
-
#
-
# @param filename [String] The path to the Sass, SCSS, or CSS file on disk.
-
# @param options [{Symbol => Object}] An options hash;
-
# see {file:SASS_REFERENCE.md#sass_options the Sass options documentation}
-
# @raise [Sass::SyntaxError] if there's an error in the document
-
# @raise [Encoding::UndefinedConversionError] if the source encoding
-
# cannot be converted to UTF-8
-
# @raise [ArgumentError] if the document uses an unknown encoding with `@charset`
-
#
-
# @overload compile_file(filename, options = {})
-
# Return the compiled CSS rather than writing it to a file.
-
#
-
# @return [String] The compiled CSS.
-
#
-
# @overload compile_file(filename, css_filename, options = {})
-
# Write the compiled CSS to a file.
-
#
-
# @param css_filename [String] The location to which to write the compiled CSS.
-
1
def self.compile_file(filename, *args)
-
options = args.last.is_a?(Hash) ? args.pop : {}
-
css_filename = args.shift
-
result = Sass::Engine.for_file(filename, options).render
-
if css_filename
-
options[:css_filename] ||= css_filename
-
open(css_filename,"w") {|css_file| css_file.write(result)}
-
nil
-
else
-
result
-
end
-
end
-
end
-
-
1
require 'sass/logger'
-
1
require 'sass/util'
-
-
1
require 'sass/engine'
-
1
require 'sass/plugin' if defined?(Merb::Plugins)
-
1
require 'sass/railtie'
-
1
require 'stringio'
-
-
1
module Sass
-
# Sass cache stores are in charge of storing cached information,
-
# especially parse trees for Sass documents.
-
#
-
# User-created importers must inherit from {CacheStores::Base}.
-
1
module CacheStores
-
end
-
end
-
-
1
require 'sass/cache_stores/base'
-
1
require 'sass/cache_stores/filesystem'
-
1
require 'sass/cache_stores/memory'
-
1
require 'sass/cache_stores/chain'
-
1
module Sass
-
1
module CacheStores
-
# An abstract base class for backends for the Sass cache.
-
# Any key-value store can act as such a backend;
-
# it just needs to implement the
-
# \{#_store} and \{#_retrieve} methods.
-
#
-
# To use a cache store with Sass,
-
# use the {file:SASS_REFERENCE.md#cache_store-option `:cache_store` option}.
-
#
-
# @abstract
-
1
class Base
-
# Store cached contents for later retrieval
-
# Must be implemented by all CacheStore subclasses
-
#
-
# Note: cache contents contain binary data.
-
#
-
# @param key [String] The key to store the contents under
-
# @param version [String] The current sass version.
-
# Cached contents must not be retrieved across different versions of sass.
-
# @param sha [String] The sha of the sass source.
-
# Cached contents must not be retrieved if the sha has changed.
-
# @param contents [String] The contents to store.
-
1
def _store(key, version, sha, contents)
-
raise "#{self.class} must implement #_store."
-
end
-
-
# Retrieved cached contents.
-
# Must be implemented by all subclasses.
-
#
-
# Note: if the key exists but the sha or version have changed,
-
# then the key may be deleted by the cache store, if it wants to do so.
-
#
-
# @param key [String] The key to retrieve
-
# @param version [String] The current sass version.
-
# Cached contents must not be retrieved across different versions of sass.
-
# @param sha [String] The sha of the sass source.
-
# Cached contents must not be retrieved if the sha has changed.
-
# @return [String] The contents that were previously stored.
-
# @return [NilClass] when the cache key is not found or the version or sha have changed.
-
1
def _retrieve(key, version, sha)
-
raise "#{self.class} must implement #_retrieve."
-
end
-
-
# Store a {Sass::Tree::RootNode}.
-
#
-
# @param key [String] The key to store it under.
-
# @param sha [String] The checksum for the contents that are being stored.
-
# @param obj [Object] The object to cache.
-
1
def store(key, sha, root)
-
_store(key, Sass::VERSION, sha, Marshal.dump(root))
-
rescue TypeError, LoadError => e
-
Sass::Util.sass_warn "Warning. Error encountered while saving cache #{path_to(key)}: #{e}"
-
end
-
-
# Retrieve a {Sass::Tree::RootNode}.
-
#
-
# @param key [String] The key the root element was stored under.
-
# @param sha [String] The checksum of the root element's content.
-
# @return [Object] The cached object.
-
1
def retrieve(key, sha)
-
contents = _retrieve(key, Sass::VERSION, sha)
-
Marshal.load(contents) if contents
-
rescue EOFError, TypeError, ArgumentError, LoadError => e
-
Sass::Util.sass_warn "Warning. Error encountered while reading cache #{path_to(key)}: #{e}"
-
end
-
-
# Return the key for the sass file.
-
#
-
# The `(sass_dirname, sass_basename)` pair
-
# should uniquely identify the Sass document,
-
# but otherwise there are no restrictions on their content.
-
#
-
# @param sass_dirname [String]
-
# The fully-expanded location of the Sass file.
-
# This corresponds to the directory name on a filesystem.
-
# @param sass_basename [String] The name of the Sass file that is being referenced.
-
# This corresponds to the basename on a filesystem.
-
1
def key(sass_dirname, sass_basename)
-
dir = Digest::SHA1.hexdigest(sass_dirname)
-
filename = "#{sass_basename}c"
-
"#{dir}/#{filename}"
-
end
-
end
-
end
-
end
-
1
module Sass
-
1
module CacheStores
-
# A meta-cache that chains multiple caches together.
-
# Specifically:
-
#
-
# * All `#store`s are passed to all caches.
-
# * `#retrieve`s are passed to each cache until one has a hit.
-
# * When one cache has a hit, the value is `#store`d in all earlier caches.
-
1
class Chain < Base
-
# Create a new cache chaining the given caches.
-
#
-
# @param caches [Array<Sass::CacheStores::Base>] The caches to chain.
-
1
def initialize(*caches)
-
@caches = caches
-
end
-
-
# @see Base#store
-
1
def store(key, sha, obj)
-
@caches.each {|c| c.store(key, sha, obj)}
-
end
-
-
# @see Base#retrieve
-
1
def retrieve(key, sha)
-
@caches.each_with_index do |c, i|
-
next unless obj = c.retrieve(key, sha)
-
@caches[0...i].each {|c| c.store(key, sha, obj)}
-
return obj
-
end
-
nil
-
end
-
end
-
end
-
end
-
1
require 'fileutils'
-
-
1
module Sass
-
1
module CacheStores
-
# A backend for the Sass cache using the filesystem.
-
1
class Filesystem < Base
-
# The directory where the cached files will be stored.
-
#
-
# @return [String]
-
1
attr_accessor :cache_location
-
-
# @param cache_location [String] see \{#cache\_location}
-
1
def initialize(cache_location)
-
@cache_location = cache_location
-
end
-
-
# @see Base#\_retrieve
-
1
def _retrieve(key, version, sha)
-
return unless File.readable?(path_to(key))
-
contents = nil
-
File.open(path_to(key), "rb") do |f|
-
if f.readline("\n").strip == version && f.readline("\n").strip == sha
-
return f.read
-
end
-
end
-
File.unlink path_to(key)
-
nil
-
rescue EOFError, TypeError, ArgumentError => e
-
Sass::Util.sass_warn "Warning. Error encountered while reading cache #{path_to(key)}: #{e}"
-
end
-
-
# @see Base#\_store
-
1
def _store(key, version, sha, contents)
-
# return unless File.writable?(File.dirname(@cache_location))
-
# return if File.exists?(@cache_location) && !File.writable?(@cache_location)
-
compiled_filename = path_to(key)
-
# return if File.exists?(File.dirname(compiled_filename)) && !File.writable?(File.dirname(compiled_filename))
-
# return if File.exists?(compiled_filename) && !File.writable?(compiled_filename)
-
FileUtils.mkdir_p(File.dirname(compiled_filename))
-
File.open(compiled_filename, "wb") do |f|
-
f.puts(version)
-
f.puts(sha)
-
f.write(contents)
-
end
-
rescue Errno::EACCES
-
#pass
-
end
-
-
1
private
-
-
# Returns the path to a file for the given key.
-
#
-
# @param key [String]
-
# @return [String] The path to the cache file.
-
1
def path_to(key)
-
key = key.gsub(/[<>:\\|?*%]/) {|c| "%%%03d" % Sass::Util.ord(c)}
-
File.join(cache_location, key)
-
end
-
end
-
end
-
end
-
1
module Sass
-
1
module CacheStores
-
# A backend for the Sass cache using in-process memory.
-
1
class Memory < Base
-
# Since the {Memory} store is stored in the Sass tree's options hash,
-
# when the options get serialized as part of serializing the tree,
-
# you get crazy exponential growth in the size of the cached objects
-
# unless you don't dump the cache.
-
#
-
# @private
-
1
def _dump(depth)
-
""
-
end
-
-
# If we deserialize this class, just make a new empty one.
-
#
-
# @private
-
1
def self._load(repr)
-
Memory.new
-
end
-
-
# Create a new, empty cache store.
-
1
def initialize
-
@contents = {}
-
end
-
-
# @see Base#retrieve
-
1
def retrieve(key, sha)
-
if @contents.has_key?(key)
-
return unless @contents[key][:sha] == sha
-
obj = @contents[key][:obj]
-
obj.respond_to?(:deep_copy) ? obj.deep_copy : obj.dup
-
end
-
end
-
-
# @see Base#store
-
1
def store(key, sha, obj)
-
@contents[key] = {:sha => sha, :obj => obj}
-
end
-
-
# Destructively clear the cache.
-
1
def reset!
-
@contents = {}
-
end
-
end
-
end
-
end
-
1
require 'set'
-
1
require 'digest/sha1'
-
1
require 'sass/cache_stores'
-
1
require 'sass/tree/node'
-
1
require 'sass/tree/root_node'
-
1
require 'sass/tree/rule_node'
-
1
require 'sass/tree/comment_node'
-
1
require 'sass/tree/prop_node'
-
1
require 'sass/tree/directive_node'
-
1
require 'sass/tree/media_node'
-
1
require 'sass/tree/variable_node'
-
1
require 'sass/tree/mixin_def_node'
-
1
require 'sass/tree/mixin_node'
-
1
require 'sass/tree/function_node'
-
1
require 'sass/tree/return_node'
-
1
require 'sass/tree/extend_node'
-
1
require 'sass/tree/if_node'
-
1
require 'sass/tree/while_node'
-
1
require 'sass/tree/for_node'
-
1
require 'sass/tree/each_node'
-
1
require 'sass/tree/debug_node'
-
1
require 'sass/tree/warn_node'
-
1
require 'sass/tree/import_node'
-
1
require 'sass/tree/charset_node'
-
1
require 'sass/tree/visitors/base'
-
1
require 'sass/tree/visitors/perform'
-
1
require 'sass/tree/visitors/cssize'
-
1
require 'sass/tree/visitors/convert'
-
1
require 'sass/tree/visitors/to_css'
-
1
require 'sass/tree/visitors/deep_copy'
-
1
require 'sass/tree/visitors/set_options'
-
1
require 'sass/tree/visitors/check_nesting'
-
1
require 'sass/selector'
-
1
require 'sass/environment'
-
1
require 'sass/script'
-
1
require 'sass/scss'
-
1
require 'sass/error'
-
1
require 'sass/importers'
-
1
require 'sass/shared'
-
-
1
module Sass
-
-
# A Sass mixin or function.
-
#
-
# `name`: `String`
-
# : The name of the mixin/function.
-
#
-
# `args`: `Array<(String, Script::Node)>`
-
# : The arguments for the mixin/function.
-
# Each element is a tuple containing the name of the argument
-
# and the parse tree for the default value of the argument.
-
#
-
# `environment`: {Sass::Environment}
-
# : The environment in which the mixin/function was defined.
-
# This is captured so that the mixin/function can have access
-
# to local variables defined in its scope.
-
#
-
# `tree`: `Array<Tree::Node>`
-
# : The parse tree for the mixin/function.
-
1
Callable = Struct.new(:name, :args, :environment, :tree)
-
-
# This class handles the parsing and compilation of the Sass template.
-
# Example usage:
-
#
-
# template = File.load('stylesheets/sassy.sass')
-
# sass_engine = Sass::Engine.new(template)
-
# output = sass_engine.render
-
# puts output
-
1
class Engine
-
1
include Sass::Util
-
-
# A line of Sass code.
-
#
-
# `text`: `String`
-
# : The text in the line, without any whitespace at the beginning or end.
-
#
-
# `tabs`: `Fixnum`
-
# : The level of indentation of the line.
-
#
-
# `index`: `Fixnum`
-
# : The line number in the original document.
-
#
-
# `offset`: `Fixnum`
-
# : The number of bytes in on the line that the text begins.
-
# This ends up being the number of bytes of leading whitespace.
-
#
-
# `filename`: `String`
-
# : The name of the file in which this line appeared.
-
#
-
# `children`: `Array<Line>`
-
# : The lines nested below this one.
-
#
-
# `comment_tab_str`: `String?`
-
# : The prefix indentation for this comment, if it is a comment.
-
1
class Line < Struct.new(:text, :tabs, :index, :offset, :filename, :children, :comment_tab_str)
-
1
def comment?
-
text[0] == COMMENT_CHAR && (text[1] == SASS_COMMENT_CHAR || text[1] == CSS_COMMENT_CHAR)
-
end
-
end
-
-
# The character that begins a CSS property.
-
1
PROPERTY_CHAR = ?:
-
-
# The character that designates the beginning of a comment,
-
# either Sass or CSS.
-
1
COMMENT_CHAR = ?/
-
-
# The character that follows the general COMMENT_CHAR and designates a Sass comment,
-
# which is not output as a CSS comment.
-
1
SASS_COMMENT_CHAR = ?/
-
-
# The character that indicates that a comment allows interpolation
-
# and should be preserved even in `:compressed` mode.
-
1
SASS_LOUD_COMMENT_CHAR = ?!
-
-
# The character that follows the general COMMENT_CHAR and designates a CSS comment,
-
# which is embedded in the CSS document.
-
1
CSS_COMMENT_CHAR = ?*
-
-
# The character used to denote a compiler directive.
-
1
DIRECTIVE_CHAR = ?@
-
-
# Designates a non-parsed rule.
-
1
ESCAPE_CHAR = ?\\
-
-
# Designates block as mixin definition rather than CSS rules to output
-
1
MIXIN_DEFINITION_CHAR = ?=
-
-
# Includes named mixin declared using MIXIN_DEFINITION_CHAR
-
1
MIXIN_INCLUDE_CHAR = ?+
-
-
# The regex that matches and extracts data from
-
# properties of the form `:name prop`.
-
1
PROPERTY_OLD = /^:([^\s=:"]+)\s*(?:\s+|$)(.*)/
-
-
# The default options for Sass::Engine.
-
# @api public
-
1
DEFAULT_OPTIONS = {
-
:style => :nested,
-
:load_paths => ['.'],
-
:cache => true,
-
:cache_location => './.sass-cache',
-
:syntax => :sass,
-
:filesystem_importer => Sass::Importers::Filesystem
-
}.freeze
-
-
# Converts a Sass options hash into a standard form, filling in
-
# default values and resolving aliases.
-
#
-
# @param options [{Symbol => Object}] The options hash;
-
# see {file:SASS_REFERENCE.md#sass_options the Sass options documentation}
-
# @return [{Symbol => Object}] The normalized options hash.
-
# @private
-
1
def self.normalize_options(options)
-
options = DEFAULT_OPTIONS.merge(options.reject {|k, v| v.nil?})
-
-
# If the `:filename` option is passed in without an importer,
-
# assume it's using the default filesystem importer.
-
options[:importer] ||= options[:filesystem_importer].new(".") if options[:filename]
-
-
# Tracks the original filename of the top-level Sass file
-
options[:original_filename] ||= options[:filename]
-
-
options[:cache_store] ||= Sass::CacheStores::Chain.new(
-
Sass::CacheStores::Memory.new, Sass::CacheStores::Filesystem.new(options[:cache_location]))
-
# Support both, because the docs said one and the other actually worked
-
# for quite a long time.
-
options[:line_comments] ||= options[:line_numbers]
-
-
options[:load_paths] = options[:load_paths].map do |p|
-
next p unless p.is_a?(String) || (defined?(Pathname) && p.is_a?(Pathname))
-
options[:filesystem_importer].new(p.to_s)
-
end
-
-
# Backwards compatibility
-
options[:property_syntax] ||= options[:attribute_syntax]
-
case options[:property_syntax]
-
when :alternate; options[:property_syntax] = :new
-
when :normal; options[:property_syntax] = :old
-
end
-
-
options
-
end
-
-
# Returns the {Sass::Engine} for the given file.
-
# This is preferable to Sass::Engine.new when reading from a file
-
# because it properly sets up the Engine's metadata,
-
# enables parse-tree caching,
-
# and infers the syntax from the filename.
-
#
-
# @param filename [String] The path to the Sass or SCSS file
-
# @param options [{Symbol => Object}] The options hash;
-
# See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
-
# @return [Sass::Engine] The Engine for the given Sass or SCSS file.
-
# @raise [Sass::SyntaxError] if there's an error in the document.
-
1
def self.for_file(filename, options)
-
had_syntax = options[:syntax]
-
-
if had_syntax
-
# Use what was explicitly specificed
-
elsif filename =~ /\.scss$/
-
options.merge!(:syntax => :scss)
-
elsif filename =~ /\.sass$/
-
options.merge!(:syntax => :sass)
-
end
-
-
Sass::Engine.new(File.read(filename), options.merge(:filename => filename))
-
end
-
-
# The options for the Sass engine.
-
# See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
-
#
-
# @return [{Symbol => Object}]
-
1
attr_reader :options
-
-
# Creates a new Engine. Note that Engine should only be used directly
-
# when compiling in-memory Sass code.
-
# If you're compiling a single Sass file from the filesystem,
-
# use \{Sass::Engine.for\_file}.
-
# If you're compiling multiple files from the filesystem,
-
# use {Sass::Plugin}.
-
#
-
# @param template [String] The Sass template.
-
# This template can be encoded using any encoding
-
# that can be converted to Unicode.
-
# If the template contains an `@charset` declaration,
-
# that overrides the Ruby encoding
-
# (see {file:SASS_REFERENCE.md#encodings the encoding documentation})
-
# @param options [{Symbol => Object}] An options hash.
-
# See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
-
# @see {Sass::Engine.for_file}
-
# @see {Sass::Plugin}
-
1
def initialize(template, options={})
-
@options = self.class.normalize_options(options)
-
@template = template
-
end
-
-
# Render the template to CSS.
-
#
-
# @return [String] The CSS
-
# @raise [Sass::SyntaxError] if there's an error in the document
-
# @raise [Encoding::UndefinedConversionError] if the source encoding
-
# cannot be converted to UTF-8
-
# @raise [ArgumentError] if the document uses an unknown encoding with `@charset`
-
1
def render
-
return _render unless @options[:quiet]
-
Sass::Util.silence_sass_warnings {_render}
-
end
-
1
alias_method :to_css, :render
-
-
# Parses the document into its parse tree. Memoized.
-
#
-
# @return [Sass::Tree::Node] The root of the parse tree.
-
# @raise [Sass::SyntaxError] if there's an error in the document
-
1
def to_tree
-
@tree ||= @options[:quiet] ?
-
Sass::Util.silence_sass_warnings {_to_tree} :
-
_to_tree
-
end
-
-
# Returns the original encoding of the document,
-
# or `nil` under Ruby 1.8.
-
#
-
# @return [Encoding, nil]
-
# @raise [Encoding::UndefinedConversionError] if the source encoding
-
# cannot be converted to UTF-8
-
# @raise [ArgumentError] if the document uses an unknown encoding with `@charset`
-
1
def source_encoding
-
check_encoding!
-
@original_encoding
-
end
-
-
# Gets a set of all the documents
-
# that are (transitive) dependencies of this document,
-
# not including the document itself.
-
#
-
# @return [[Sass::Engine]] The dependency documents.
-
1
def dependencies
-
_dependencies(Set.new, engines = Set.new)
-
engines - [self]
-
end
-
-
# Helper for \{#dependencies}.
-
#
-
# @private
-
1
def _dependencies(seen, engines)
-
return if seen.include?(key = [@options[:filename], @options[:importer]])
-
seen << key
-
engines << self
-
to_tree.grep(Tree::ImportNode) do |n|
-
next if n.css_import?
-
n.imported_file._dependencies(seen, engines)
-
end
-
end
-
-
1
private
-
-
1
def _render
-
rendered = _to_tree.render
-
return rendered if ruby1_8?
-
begin
-
# Try to convert the result to the original encoding,
-
# but if that doesn't work fall back on UTF-8
-
rendered = rendered.encode(source_encoding)
-
rescue EncodingError
-
end
-
rendered.gsub(Regexp.new('\A@charset "(.*?)"'.encode(source_encoding)),
-
"@charset \"#{source_encoding.name}\"".encode(source_encoding))
-
end
-
-
1
def _to_tree
-
if (@options[:cache] || @options[:read_cache]) &&
-
@options[:filename] && @options[:importer]
-
key = sassc_key
-
sha = Digest::SHA1.hexdigest(@template)
-
-
if root = @options[:cache_store].retrieve(key, sha)
-
root.options = @options
-
return root
-
end
-
end
-
-
check_encoding!
-
-
if @options[:syntax] == :scss
-
root = Sass::SCSS::Parser.new(@template, @options[:filename]).parse
-
else
-
root = Tree::RootNode.new(@template)
-
append_children(root, tree(tabulate(@template)).first, true)
-
end
-
-
root.options = @options
-
if @options[:cache] && key && sha
-
begin
-
old_options = root.options
-
root.options = {}
-
@options[:cache_store].store(key, sha, root)
-
ensure
-
root.options = old_options
-
end
-
end
-
root
-
rescue SyntaxError => e
-
e.modify_backtrace(:filename => @options[:filename], :line => @line)
-
e.sass_template = @template
-
raise e
-
end
-
-
1
def sassc_key
-
@options[:cache_store].key(*@options[:importer].key(@options[:filename], @options))
-
end
-
-
1
def check_encoding!
-
return if @checked_encoding
-
@checked_encoding = true
-
@template, @original_encoding = check_sass_encoding(@template) do |msg, line|
-
raise Sass::SyntaxError.new(msg, :line => line)
-
end
-
end
-
-
1
def tabulate(string)
-
tab_str = nil
-
comment_tab_str = nil
-
first = true
-
lines = []
-
string.gsub(/\r|\n|\r\n|\r\n/, "\n").scan(/^[^\n]*?$/).each_with_index do |line, index|
-
index += (@options[:line] || 1)
-
if line.strip.empty?
-
lines.last.text << "\n" if lines.last && lines.last.comment?
-
next
-
end
-
-
line_tab_str = line[/^\s*/]
-
unless line_tab_str.empty?
-
if tab_str.nil?
-
comment_tab_str ||= line_tab_str
-
next if try_comment(line, lines.last, "", comment_tab_str, index)
-
comment_tab_str = nil
-
end
-
-
tab_str ||= line_tab_str
-
-
raise SyntaxError.new("Indenting at the beginning of the document is illegal.",
-
:line => index) if first
-
-
raise SyntaxError.new("Indentation can't use both tabs and spaces.",
-
:line => index) if tab_str.include?(?\s) && tab_str.include?(?\t)
-
end
-
first &&= !tab_str.nil?
-
if tab_str.nil?
-
lines << Line.new(line.strip, 0, index, 0, @options[:filename], [])
-
next
-
end
-
-
comment_tab_str ||= line_tab_str
-
if try_comment(line, lines.last, tab_str * lines.last.tabs, comment_tab_str, index)
-
next
-
else
-
comment_tab_str = nil
-
end
-
-
line_tabs = line_tab_str.scan(tab_str).size
-
if tab_str * line_tabs != line_tab_str
-
message = <<END.strip.gsub("\n", ' ')
-
Inconsistent indentation: #{Sass::Shared.human_indentation line_tab_str, true} used for indentation,
-
but the rest of the document was indented using #{Sass::Shared.human_indentation tab_str}.
-
END
-
raise SyntaxError.new(message, :line => index)
-
end
-
-
lines << Line.new(line.strip, line_tabs, index, tab_str.size, @options[:filename], [])
-
end
-
lines
-
end
-
-
1
def try_comment(line, last, tab_str, comment_tab_str, index)
-
return unless last && last.comment?
-
# Nested comment stuff must be at least one whitespace char deeper
-
# than the normal indentation
-
return unless line =~ /^#{tab_str}\s/
-
unless line =~ /^(?:#{comment_tab_str})(.*)$/
-
raise SyntaxError.new(<<MSG.strip.gsub("\n", " "), :line => index)
-
Inconsistent indentation:
-
previous line was indented by #{Sass::Shared.human_indentation comment_tab_str},
-
but this line was indented by #{Sass::Shared.human_indentation line[/^\s*/]}.
-
MSG
-
end
-
-
last.comment_tab_str ||= comment_tab_str
-
last.text << "\n" << line
-
true
-
end
-
-
1
def tree(arr, i = 0)
-
return [], i if arr[i].nil?
-
-
base = arr[i].tabs
-
nodes = []
-
while (line = arr[i]) && line.tabs >= base
-
if line.tabs > base
-
raise SyntaxError.new("The line was indented #{line.tabs - base} levels deeper than the previous line.",
-
:line => line.index) if line.tabs > base + 1
-
-
nodes.last.children, i = tree(arr, i)
-
else
-
nodes << line
-
i += 1
-
end
-
end
-
return nodes, i
-
end
-
-
1
def build_tree(parent, line, root = false)
-
@line = line.index
-
node_or_nodes = parse_line(parent, line, root)
-
-
Array(node_or_nodes).each do |node|
-
# Node is a symbol if it's non-outputting, like a variable assignment
-
next unless node.is_a? Tree::Node
-
-
node.line = line.index
-
node.filename = line.filename
-
-
append_children(node, line.children, false)
-
end
-
-
node_or_nodes
-
end
-
-
1
def append_children(parent, children, root)
-
continued_rule = nil
-
continued_comment = nil
-
children.each do |line|
-
child = build_tree(parent, line, root)
-
-
if child.is_a?(Tree::RuleNode)
-
if child.continued? && child.children.empty?
-
if continued_rule
-
continued_rule.add_rules child
-
else
-
continued_rule = child
-
end
-
next
-
elsif continued_rule
-
continued_rule.add_rules child
-
continued_rule.children = child.children
-
continued_rule, child = nil, continued_rule
-
end
-
elsif continued_rule
-
continued_rule = nil
-
end
-
-
if child.is_a?(Tree::CommentNode) && child.silent
-
if continued_comment &&
-
child.line == continued_comment.line +
-
continued_comment.lines + 1
-
continued_comment.value += ["\n"] + child.value
-
next
-
end
-
-
continued_comment = child
-
end
-
-
check_for_no_children(child)
-
validate_and_append_child(parent, child, line, root)
-
end
-
-
parent
-
end
-
-
1
def validate_and_append_child(parent, child, line, root)
-
case child
-
when Array
-
child.each {|c| validate_and_append_child(parent, c, line, root)}
-
when Tree::Node
-
parent << child
-
end
-
end
-
-
1
def check_for_no_children(node)
-
return unless node.is_a?(Tree::RuleNode) && node.children.empty?
-
Sass::Util.sass_warn(<<WARNING.strip)
-
WARNING on line #{node.line}#{" of #{node.filename}" if node.filename}:
-
This selector doesn't have any properties and will not be rendered.
-
WARNING
-
end
-
-
1
def parse_line(parent, line, root)
-
case line.text[0]
-
when PROPERTY_CHAR
-
if line.text[1] == PROPERTY_CHAR ||
-
(@options[:property_syntax] == :new &&
-
line.text =~ PROPERTY_OLD && $2.empty?)
-
# Support CSS3-style pseudo-elements,
-
# which begin with ::,
-
# as well as pseudo-classes
-
# if we're using the new property syntax
-
Tree::RuleNode.new(parse_interp(line.text))
-
else
-
name, value = line.text.scan(PROPERTY_OLD)[0]
-
raise SyntaxError.new("Invalid property: \"#{line.text}\".",
-
:line => @line) if name.nil? || value.nil?
-
parse_property(name, parse_interp(name), value, :old, line)
-
end
-
when ?$
-
parse_variable(line)
-
when COMMENT_CHAR
-
parse_comment(line)
-
when DIRECTIVE_CHAR
-
parse_directive(parent, line, root)
-
when ESCAPE_CHAR
-
Tree::RuleNode.new(parse_interp(line.text[1..-1]))
-
when MIXIN_DEFINITION_CHAR
-
parse_mixin_definition(line)
-
when MIXIN_INCLUDE_CHAR
-
if line.text[1].nil? || line.text[1] == ?\s
-
Tree::RuleNode.new(parse_interp(line.text))
-
else
-
parse_mixin_include(line, root)
-
end
-
else
-
parse_property_or_rule(line)
-
end
-
end
-
-
1
def parse_property_or_rule(line)
-
scanner = Sass::Util::MultibyteStringScanner.new(line.text)
-
hack_char = scanner.scan(/[:\*\.]|\#(?!\{)/)
-
parser = Sass::SCSS::SassParser.new(scanner, @options[:filename], @line)
-
-
unless res = parser.parse_interp_ident
-
return Tree::RuleNode.new(parse_interp(line.text))
-
end
-
res.unshift(hack_char) if hack_char
-
if comment = scanner.scan(Sass::SCSS::RX::COMMENT)
-
res << comment
-
end
-
-
name = line.text[0...scanner.pos]
-
if scanner.scan(/\s*:(?:\s|$)/)
-
parse_property(name, res, scanner.rest, :new, line)
-
else
-
res.pop if comment
-
Tree::RuleNode.new(res + parse_interp(scanner.rest))
-
end
-
end
-
-
1
def parse_property(name, parsed_name, value, prop, line)
-
if value.strip.empty?
-
expr = Sass::Script::String.new("")
-
else
-
expr = parse_script(value, :offset => line.offset + line.text.index(value))
-
end
-
Tree::PropNode.new(parse_interp(name), expr, prop)
-
end
-
-
1
def parse_variable(line)
-
name, value, default = line.text.scan(Script::MATCH)[0]
-
raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath variable declarations.",
-
:line => @line + 1) unless line.children.empty?
-
raise SyntaxError.new("Invalid variable: \"#{line.text}\".",
-
:line => @line) unless name && value
-
-
expr = parse_script(value, :offset => line.offset + line.text.index(value))
-
-
Tree::VariableNode.new(name, expr, default)
-
end
-
-
1
def parse_comment(line)
-
if line.text[1] == CSS_COMMENT_CHAR || line.text[1] == SASS_COMMENT_CHAR
-
silent = line.text[1] == SASS_COMMENT_CHAR
-
if loud = line.text[2] == SASS_LOUD_COMMENT_CHAR
-
value = self.class.parse_interp(line.text, line.index, line.offset, :filename => @filename)
-
value[0].slice!(2) # get rid of the "!"
-
else
-
value = [line.text]
-
end
-
value = with_extracted_values(value) do |str|
-
str = str.gsub(/^#{line.comment_tab_str}/m, '')[2..-1] # get rid of // or /*
-
format_comment_text(str, silent)
-
end
-
Tree::CommentNode.new(value, silent, loud)
-
else
-
Tree::RuleNode.new(parse_interp(line.text))
-
end
-
end
-
-
1
def parse_directive(parent, line, root)
-
directive, whitespace, value = line.text[1..-1].split(/(\s+)/, 2)
-
offset = directive.size + whitespace.size + 1 if whitespace
-
-
# If value begins with url( or ",
-
# it's a CSS @import rule and we don't want to touch it.
-
if directive == "import"
-
parse_import(line, value)
-
elsif directive == "mixin"
-
parse_mixin_definition(line)
-
elsif directive == "include"
-
parse_mixin_include(line, root)
-
elsif directive == "function"
-
parse_function(line, root)
-
elsif directive == "for"
-
parse_for(line, root, value)
-
elsif directive == "each"
-
parse_each(line, root, value)
-
elsif directive == "else"
-
parse_else(parent, line, value)
-
elsif directive == "while"
-
raise SyntaxError.new("Invalid while directive '@while': expected expression.") unless value
-
Tree::WhileNode.new(parse_script(value, :offset => offset))
-
elsif directive == "if"
-
raise SyntaxError.new("Invalid if directive '@if': expected expression.") unless value
-
Tree::IfNode.new(parse_script(value, :offset => offset))
-
elsif directive == "debug"
-
raise SyntaxError.new("Invalid debug directive '@debug': expected expression.") unless value
-
raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath debug directives.",
-
:line => @line + 1) unless line.children.empty?
-
offset = line.offset + line.text.index(value).to_i
-
Tree::DebugNode.new(parse_script(value, :offset => offset))
-
elsif directive == "extend"
-
raise SyntaxError.new("Invalid extend directive '@extend': expected expression.") unless value
-
raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath extend directives.",
-
:line => @line + 1) unless line.children.empty?
-
offset = line.offset + line.text.index(value).to_i
-
Tree::ExtendNode.new(parse_interp(value, offset))
-
elsif directive == "warn"
-
raise SyntaxError.new("Invalid warn directive '@warn': expected expression.") unless value
-
raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath warn directives.",
-
:line => @line + 1) unless line.children.empty?
-
offset = line.offset + line.text.index(value).to_i
-
Tree::WarnNode.new(parse_script(value, :offset => offset))
-
elsif directive == "return"
-
raise SyntaxError.new("Invalid @return: expected expression.") unless value
-
raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath return directives.",
-
:line => @line + 1) unless line.children.empty?
-
offset = line.offset + line.text.index(value).to_i
-
Tree::ReturnNode.new(parse_script(value, :offset => offset))
-
elsif directive == "charset"
-
name = value && value[/\A(["'])(.*)\1\Z/, 2] #"
-
raise SyntaxError.new("Invalid charset directive '@charset': expected string.") unless name
-
raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath charset directives.",
-
:line => @line + 1) unless line.children.empty?
-
Tree::CharsetNode.new(name)
-
elsif directive == "media"
-
Tree::MediaNode.new(value.split(',').map {|s| s.strip})
-
else
-
Tree::DirectiveNode.new(line.text)
-
end
-
end
-
-
1
def parse_for(line, root, text)
-
var, from_expr, to_name, to_expr = text.scan(/^([^\s]+)\s+from\s+(.+)\s+(to|through)\s+(.+)$/).first
-
-
if var.nil? # scan failed, try to figure out why for error message
-
if text !~ /^[^\s]+/
-
expected = "variable name"
-
elsif text !~ /^[^\s]+\s+from\s+.+/
-
expected = "'from <expr>'"
-
else
-
expected = "'to <expr>' or 'through <expr>'"
-
end
-
raise SyntaxError.new("Invalid for directive '@for #{text}': expected #{expected}.")
-
end
-
raise SyntaxError.new("Invalid variable \"#{var}\".") unless var =~ Script::VALIDATE
-
-
var = var[1..-1]
-
parsed_from = parse_script(from_expr, :offset => line.offset + line.text.index(from_expr))
-
parsed_to = parse_script(to_expr, :offset => line.offset + line.text.index(to_expr))
-
Tree::ForNode.new(var, parsed_from, parsed_to, to_name == 'to')
-
end
-
-
1
def parse_each(line, root, text)
-
var, list_expr = text.scan(/^([^\s]+)\s+in\s+(.+)$/).first
-
-
if var.nil? # scan failed, try to figure out why for error message
-
if text !~ /^[^\s]+/
-
expected = "variable name"
-
elsif text !~ /^[^\s]+\s+from\s+.+/
-
expected = "'in <expr>'"
-
end
-
raise SyntaxError.new("Invalid for directive '@each #{text}': expected #{expected}.")
-
end
-
raise SyntaxError.new("Invalid variable \"#{var}\".") unless var =~ Script::VALIDATE
-
-
var = var[1..-1]
-
parsed_list = parse_script(list_expr, :offset => line.offset + line.text.index(list_expr))
-
Tree::EachNode.new(var, parsed_list)
-
end
-
-
1
def parse_else(parent, line, text)
-
previous = parent.children.last
-
raise SyntaxError.new("@else must come after @if.") unless previous.is_a?(Tree::IfNode)
-
-
if text
-
if text !~ /^if\s+(.+)/
-
raise SyntaxError.new("Invalid else directive '@else #{text}': expected 'if <expr>'.")
-
end
-
expr = parse_script($1, :offset => line.offset + line.text.index($1))
-
end
-
-
node = Tree::IfNode.new(expr)
-
append_children(node, line.children, false)
-
previous.add_else node
-
nil
-
end
-
-
1
def parse_import(line, value)
-
raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath import directives.",
-
:line => @line + 1) unless line.children.empty?
-
-
scanner = Sass::Util::MultibyteStringScanner.new(value)
-
values = []
-
-
loop do
-
unless node = parse_import_arg(scanner)
-
raise SyntaxError.new("Invalid @import: expected file to import, was #{scanner.rest.inspect}",
-
:line => @line)
-
end
-
values << node
-
break unless scanner.scan(/,\s*/)
-
end
-
-
if scanner.scan(/;/)
-
raise SyntaxError.new("Invalid @import: expected end of line, was \";\".",
-
:line => @line)
-
end
-
-
return values
-
end
-
-
1
def parse_import_arg(scanner)
-
return if scanner.eos?
-
unless (str = scanner.scan(Sass::SCSS::RX::STRING)) ||
-
(uri = scanner.scan(Sass::SCSS::RX::URI))
-
return Tree::ImportNode.new(scanner.scan(/[^,;]+/))
-
end
-
-
val = scanner[1] || scanner[2]
-
scanner.scan(/\s*/)
-
if media = scanner.scan(/[a-zA-Z].*/)
-
Tree::DirectiveNode.new("@import #{str || uri} #{media}")
-
elsif !scanner.match?(/[,;]|$/)
-
raise SyntaxError.new("Invalid @import: \"#{str || uri} #{scanner.rest}\"")
-
elsif uri
-
Tree::DirectiveNode.new("@import #{uri}")
-
elsif val =~ /^http:\/\//
-
Tree::DirectiveNode.new("@import #{str}")
-
else
-
Tree::ImportNode.new(val)
-
end
-
end
-
-
1
MIXIN_DEF_RE = /^(?:=|@mixin)\s*(#{Sass::SCSS::RX::IDENT})(.*)$/
-
1
def parse_mixin_definition(line)
-
name, arg_string = line.text.scan(MIXIN_DEF_RE).first
-
raise SyntaxError.new("Invalid mixin \"#{line.text[1..-1]}\".") if name.nil?
-
-
offset = line.offset + line.text.size - arg_string.size
-
args = Script::Parser.new(arg_string.strip, @line, offset, @options).
-
parse_mixin_definition_arglist
-
Tree::MixinDefNode.new(name, args)
-
end
-
-
1
MIXIN_INCLUDE_RE = /^(?:\+|@include)\s*(#{Sass::SCSS::RX::IDENT})(.*)$/
-
1
def parse_mixin_include(line, root)
-
name, arg_string = line.text.scan(MIXIN_INCLUDE_RE).first
-
raise SyntaxError.new("Invalid mixin include \"#{line.text}\".") if name.nil?
-
-
offset = line.offset + line.text.size - arg_string.size
-
args, keywords = Script::Parser.new(arg_string.strip, @line, offset, @options).
-
parse_mixin_include_arglist
-
raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath mixin directives.",
-
:line => @line + 1) unless line.children.empty?
-
Tree::MixinNode.new(name, args, keywords)
-
end
-
-
1
FUNCTION_RE = /^@function\s*(#{Sass::SCSS::RX::IDENT})(.*)$/
-
1
def parse_function(line, root)
-
name, arg_string = line.text.scan(FUNCTION_RE).first
-
raise SyntaxError.new("Invalid function definition \"#{line.text}\".") if name.nil?
-
-
offset = line.offset + line.text.size - arg_string.size
-
args = Script::Parser.new(arg_string.strip, @line, offset, @options).
-
parse_function_definition_arglist
-
Tree::FunctionNode.new(name, args)
-
end
-
-
1
def parse_script(script, options = {})
-
line = options[:line] || @line
-
offset = options[:offset] || 0
-
Script.parse(script, line, offset, @options)
-
end
-
-
1
def format_comment_text(text, silent)
-
content = text.split("\n")
-
-
if content.first && content.first.strip.empty?
-
removed_first = true
-
content.shift
-
end
-
-
return silent ? "//" : "/* */" if content.empty?
-
content.last.gsub!(%r{ ?\*/ *$}, '')
-
content.map! {|l| l.gsub!(/^\*( ?)/, '\1') || (l.empty? ? "" : " ") + l}
-
content.first.gsub!(/^ /, '') unless removed_first
-
if silent
-
"//" + content.join("\n//")
-
else
-
# The #gsub fixes the case of a trailing */
-
"/*" + content.join("\n *").gsub(/ \*\Z/, '') + " */"
-
end
-
end
-
-
1
def parse_interp(text, offset = 0)
-
self.class.parse_interp(text, @line, offset, :filename => @filename)
-
end
-
-
# It's important that this have strings (at least)
-
# at the beginning, the end, and between each Script::Node.
-
#
-
# @private
-
1
def self.parse_interp(text, line, offset, options)
-
res = []
-
rest = Sass::Shared.handle_interpolation text do |scan|
-
escapes = scan[2].size
-
res << scan.matched[0...-2 - escapes]
-
if escapes % 2 == 1
-
res << "\\" * (escapes - 1) << '#{'
-
else
-
res << "\\" * [0, escapes - 1].max
-
res << Script::Parser.new(
-
scan, line, offset + scan.pos - scan.matched_size, options).
-
parse_interpolated
-
end
-
end
-
res << rest
-
end
-
end
-
end
-
1
require 'set'
-
-
1
module Sass
-
# The lexical environment for SassScript.
-
# This keeps track of variable, mixin, and function definitions.
-
#
-
# A new environment is created for each level of Sass nesting.
-
# This allows variables to be lexically scoped.
-
# The new environment refers to the environment in the upper scope,
-
# so it has access to variables defined in enclosing scopes,
-
# but new variables are defined locally.
-
#
-
# Environment also keeps track of the {Engine} options
-
# so that they can be made available to {Sass::Script::Functions}.
-
1
class Environment
-
# The enclosing environment,
-
# or nil if this is the global environment.
-
#
-
# @return [Environment]
-
1
attr_reader :parent
-
1
attr_writer :options
-
-
# @param parent [Environment] See \{#parent}
-
1
def initialize(parent = nil)
-
@parent = parent
-
unless parent
-
@stack = []
-
@mixins_in_use = Set.new
-
@files_in_use = Set.new
-
end
-
end
-
-
# The options hash.
-
# See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
-
#
-
# @return [{Symbol => Object}]
-
1
def options
-
@options || parent_options || {}
-
end
-
-
# Push a new stack frame onto the mixin/include stack.
-
#
-
# @param frame_info [{Symbol => Object}]
-
# Frame information has the following keys:
-
#
-
# `:filename`
-
# : The name of the file in which the lexical scope changed.
-
#
-
# `:mixin`
-
# : The name of the mixin in which the lexical scope changed,
-
# or `nil` if it wasn't within in a mixin.
-
#
-
# `:line`
-
# : The line of the file on which the lexical scope changed. Never nil.
-
1
def push_frame(frame_info)
-
top_of_stack = stack.last
-
if top_of_stack && top_of_stack.delete(:prepared)
-
top_of_stack.merge!(frame_info)
-
else
-
stack.push(top_of_stack = frame_info)
-
end
-
mixins_in_use << top_of_stack[:mixin] if top_of_stack[:mixin]
-
files_in_use << top_of_stack[:filename] if top_of_stack[:filename]
-
end
-
-
# Like \{#push\_frame}, but next time a stack frame is pushed,
-
# it will be merged with this frame.
-
#
-
# @param frame_info [{Symbol => Object}] Same as for \{#push\_frame}.
-
1
def prepare_frame(frame_info)
-
push_frame(frame_info.merge(:prepared => true))
-
end
-
-
# Pop a stack frame from the mixin/include stack.
-
1
def pop_frame
-
pop_and_unuse if stack.last && stack.last[:prepared]
-
pop_and_unuse
-
end
-
-
# A list of stack frames in the mixin/include stack.
-
# The last element in the list is the most deeply-nested frame.
-
#
-
# @return [Array<{Symbol => Object}>] The stack frames,
-
# of the form passed to \{#push\_frame}.
-
1
def stack
-
@stack ||= @parent.stack
-
end
-
-
# A set of names of mixins currently present in the stack.
-
#
-
# @return [Set<String>] The mixin names.
-
1
def mixins_in_use
-
@mixins_in_use ||= @parent.mixins_in_use
-
end
-
-
# A set of names of files currently present in the stack.
-
#
-
# @return [Set<String>] The filenames.
-
1
def files_in_use
-
@files_in_use ||= @parent.files_in_use
-
end
-
-
1
def stack_trace
-
trace = []
-
stack.reverse.each_with_index do |entry, i|
-
msg = "#{i == 0 ? "on" : "from"} line #{entry[:line]}"
-
msg << " of #{entry[:filename] || "an unknown file"}"
-
msg << ", in `#{entry[:mixin]}'" if entry[:mixin]
-
trace << msg
-
end
-
trace
-
end
-
-
1
private
-
-
1
def pop_and_unuse
-
popped = stack.pop
-
mixins_in_use.delete(popped[:mixin]) if popped && popped[:mixin]
-
files_in_use.delete(popped[:filename]) if popped && popped[:filename]
-
popped
-
end
-
-
1
def parent_options
-
@parent_options ||= @parent && @parent.options
-
end
-
-
1
class << self
-
1
private
-
1
UNDERSCORE, DASH = '_', '-'
-
-
# Note: when updating this,
-
# update sass/yard/inherited_hash.rb as well.
-
1
def inherited_hash(name)
-
3
class_eval <<RUBY, __FILE__, __LINE__ + 1
-
def #{name}(name)
-
_#{name}(name.tr(UNDERSCORE, DASH))
-
end
-
-
def _#{name}(name)
-
(@#{name}s && @#{name}s[name]) || @parent && @parent._#{name}(name)
-
end
-
protected :_#{name}
-
-
def set_#{name}(name, value)
-
name = name.tr(UNDERSCORE, DASH)
-
@#{name}s[name] = value unless try_set_#{name}(name, value)
-
end
-
-
def try_set_#{name}(name, value)
-
@#{name}s ||= {}
-
if @#{name}s.include?(name)
-
@#{name}s[name] = value
-
true
-
elsif @parent
-
@parent.try_set_#{name}(name, value)
-
else
-
false
-
end
-
end
-
protected :try_set_#{name}
-
-
def set_local_#{name}(name, value)
-
@#{name}s ||= {}
-
@#{name}s[name.tr(UNDERSCORE, DASH)] = value
-
end
-
RUBY
-
end
-
end
-
-
# variable
-
# Script::Literal
-
1
inherited_hash :var
-
# mixin
-
# Sass::Callable
-
1
inherited_hash :mixin
-
# function
-
# Sass::Callable
-
1
inherited_hash :function
-
end
-
end
-
1
module Sass
-
# An exception class that keeps track of
-
# the line of the Sass template it was raised on
-
# and the Sass file that was being parsed (if applicable).
-
#
-
# All Sass errors are raised as {Sass::SyntaxError}s.
-
#
-
# When dealing with SyntaxErrors,
-
# it's important to provide filename and line number information.
-
# This will be used in various error reports to users, including backtraces;
-
# see \{#sass\_backtrace} for details.
-
#
-
# Some of this information is usually provided as part of the constructor.
-
# New backtrace entries can be added with \{#add\_backtrace},
-
# which is called when an exception is raised between files (e.g. with `@import`).
-
#
-
# Often, a chunk of code will all have similar backtrace information -
-
# the same filename or even line.
-
# It may also be useful to have a default line number set.
-
# In those situations, the default values can be used
-
# by omitting the information on the original exception,
-
# and then calling \{#modify\_backtrace} in a wrapper `rescue`.
-
# When doing this, be sure that all exceptions ultimately end up
-
# with the information filled in.
-
1
class SyntaxError < StandardError
-
# The backtrace of the error within Sass files.
-
# This is an array of hashes containing information for a single entry.
-
# The hashes have the following keys:
-
#
-
# `:filename`
-
# : The name of the file in which the exception was raised,
-
# or `nil` if no filename is available.
-
#
-
# `:mixin`
-
# : The name of the mixin in which the exception was raised,
-
# or `nil` if it wasn't raised in a mixin.
-
#
-
# `:line`
-
# : The line of the file on which the error occurred. Never nil.
-
#
-
# This information is also included in standard backtrace format
-
# in the output of \{#backtrace}.
-
#
-
# @return [Aray<{Symbol => Object>}]
-
1
attr_accessor :sass_backtrace
-
-
# The text of the template where this error was raised.
-
#
-
# @return [String]
-
1
attr_accessor :sass_template
-
-
# @param msg [String] The error message
-
# @param attrs [{Symbol => Object}] The information in the backtrace entry.
-
# See \{#sass\_backtrace}
-
1
def initialize(msg, attrs = {})
-
@message = msg
-
@sass_backtrace = []
-
add_backtrace(attrs)
-
end
-
-
# The name of the file in which the exception was raised.
-
# This could be `nil` if no filename is available.
-
#
-
# @return [String, nil]
-
1
def sass_filename
-
sass_backtrace.first[:filename]
-
end
-
-
# The name of the mixin in which the error occurred.
-
# This could be `nil` if the error occurred outside a mixin.
-
#
-
# @return [Fixnum]
-
1
def sass_mixin
-
sass_backtrace.first[:mixin]
-
end
-
-
# The line of the Sass template on which the error occurred.
-
#
-
# @return [Fixnum]
-
1
def sass_line
-
sass_backtrace.first[:line]
-
end
-
-
# Adds an entry to the exception's Sass backtrace.
-
#
-
# @param attrs [{Symbol => Object}] The information in the backtrace entry.
-
# See \{#sass\_backtrace}
-
1
def add_backtrace(attrs)
-
sass_backtrace << attrs.reject {|k, v| v.nil?}
-
end
-
-
# Modify the top Sass backtrace entries
-
# (that is, the most deeply nested ones)
-
# to have the given attributes.
-
#
-
# Specifically, this goes through the backtrace entries
-
# from most deeply nested to least,
-
# setting the given attributes for each entry.
-
# If an entry already has one of the given attributes set,
-
# the pre-existing attribute takes precedence
-
# and is not used for less deeply-nested entries
-
# (even if they don't have that attribute set).
-
#
-
# @param attrs [{Symbol => Object}] The information to add to the backtrace entry.
-
# See \{#sass\_backtrace}
-
1
def modify_backtrace(attrs)
-
attrs = attrs.reject {|k, v| v.nil?}
-
# Move backwards through the backtrace
-
(0...sass_backtrace.size).to_a.reverse.each do |i|
-
entry = sass_backtrace[i]
-
sass_backtrace[i] = attrs.merge(entry)
-
attrs.reject! {|k, v| entry.include?(k)}
-
break if attrs.empty?
-
end
-
end
-
-
# @return [String] The error message
-
1
def to_s
-
@message
-
end
-
-
# Returns the standard exception backtrace,
-
# including the Sass backtrace.
-
#
-
# @return [Array<String>]
-
1
def backtrace
-
return nil if super.nil?
-
return super if sass_backtrace.all? {|h| h.empty?}
-
sass_backtrace.map do |h|
-
"#{h[:filename] || "(sass)"}:#{h[:line]}" +
-
(h[:mixin] ? ":in `#{h[:mixin]}'" : "")
-
end + super
-
end
-
-
# Returns a string representation of the Sass backtrace.
-
#
-
# @param default_filename [String] The filename to use for unknown files
-
# @see #sass_backtrace
-
# @return [String]
-
1
def sass_backtrace_str(default_filename = "an unknown file")
-
lines = self.message.split("\n")
-
msg = lines[0] + lines[1..-1].
-
map {|l| "\n" + (" " * "Syntax error: ".size) + l}.join
-
"Syntax error: #{msg}" +
-
Sass::Util.enum_with_index(sass_backtrace).map do |entry, i|
-
"\n #{i == 0 ? "on" : "from"} line #{entry[:line]}" +
-
" of #{entry[:filename] || default_filename}" +
-
(entry[:mixin] ? ", in `#{entry[:mixin]}'" : "")
-
end.join
-
end
-
-
1
class << self
-
# Returns an error report for an exception in CSS format.
-
#
-
# @param e [Exception]
-
# @param options [{Symbol => Object}] The options passed to {Sass::Engine#initialize}
-
# @return [String] The error report
-
# @raise [Exception] `e`, if the
-
# {file:SASS_REFERENCE.md#full_exception-option `:full_exception`} option
-
# is set to false.
-
1
def exception_to_css(e, options)
-
raise e unless options[:full_exception]
-
-
header = header_string(e, options)
-
-
<<END
-
/*
-
#{header}
-
-
Backtrace:\n#{e.backtrace.join("\n")}
-
*/
-
body:before {
-
white-space: pre;
-
font-family: monospace;
-
content: "#{header.gsub('"', '\"').gsub("\n", '\\A ')}"; }
-
END
-
end
-
-
1
private
-
-
1
def header_string(e, options)
-
unless e.is_a?(Sass::SyntaxError) && e.sass_line && e.sass_template
-
return "#{e.class}: #{e.message}"
-
end
-
-
line_offset = options[:line] || 1
-
line_num = e.sass_line + 1 - line_offset
-
min = [line_num - 6, 0].max
-
section = e.sass_template.rstrip.split("\n")[min ... line_num + 5]
-
return e.sass_backtrace_str if section.nil? || section.empty?
-
-
e.sass_backtrace_str + "\n\n" + Sass::Util.enum_with_index(section).
-
map {|line, i| "#{line_offset + min + i}: #{line}"}.join("\n")
-
end
-
end
-
end
-
-
# The class for Sass errors that are raised due to invalid unit conversions
-
# in SassScript.
-
1
class UnitConversionError < SyntaxError; end
-
end
-
1
module Sass
-
# Sass importers are in charge of taking paths passed to `@import`
-
# and finding the appropriate Sass code for those paths.
-
# By default, this code is always loaded from the filesystem,
-
# but importers could be added to load from a database or over HTTP.
-
#
-
# Each importer is in charge of a single load path
-
# (or whatever the corresponding notion is for the backend).
-
# Importers can be placed in the {file:SASS_REFERENCE.md#load_paths-option `:load_paths` array}
-
# alongside normal filesystem paths.
-
#
-
# When resolving an `@import`, Sass will go through the load paths
-
# looking for an importer that successfully imports the path.
-
# Once one is found, the imported file is used.
-
#
-
# User-created importers must inherit from {Importers::Base}.
-
1
module Importers
-
end
-
end
-
-
1
require 'sass/importers/base'
-
1
require 'sass/importers/filesystem'
-
1
module Sass
-
1
module Importers
-
# The abstract base class for Sass importers.
-
# All importers should inherit from this.
-
#
-
# At the most basic level, an importer is given a string
-
# and must return a {Sass::Engine} containing some Sass code.
-
# This string can be interpreted however the importer wants;
-
# however, subclasses are encouraged to use the URI format
-
# for pathnames.
-
#
-
# Importers that have some notion of "relative imports"
-
# should take a single load path in their constructor,
-
# and interpret paths as relative to that.
-
# They should also implement the \{#find\_relative} method.
-
#
-
# Importers should be serializable via `Marshal.dump`.
-
# In addition to the standard `_dump` and `_load` methods,
-
# importers can define `_before_dump`, `_after_dump`, `_around_dump`,
-
# and `_after_load` methods as per {Sass::Util#dump} and {Sass::Util#load}.
-
#
-
# @abstract
-
1
class Base
-
-
# Find a Sass file relative to another file.
-
# Importers without a notion of "relative paths"
-
# should just return nil here.
-
#
-
# If the importer does have a notion of "relative paths",
-
# it should ignore its load path during this method.
-
#
-
# See \{#find} for important information on how this method should behave.
-
#
-
# The `:filename` option passed to the returned {Sass::Engine}
-
# should be of a format that could be passed to \{#find}.
-
#
-
# @param uri [String] The URI to import. This is not necessarily relative,
-
# but this method should only return true if it is.
-
# @param base [String] The base filename. If `uri` is relative,
-
# it should be interpreted as relative to `base`.
-
# `base` is guaranteed to be in a format importable by this importer.
-
# @param options [{Symbol => Object}] Options for the Sass file
-
# containing the `@import` that's currently being resolved.
-
# @return [Sass::Engine, nil] An Engine containing the imported file,
-
# or nil if it couldn't be found or was in the wrong format.
-
1
def find_relative(uri, base, options)
-
Sass::Util.abstract(self)
-
end
-
-
# Find a Sass file, if it exists.
-
#
-
# This is the primary entry point of the Importer.
-
# It corresponds directly to an `@import` statement in Sass.
-
# It should do three basic things:
-
#
-
# * Determine if the URI is in this importer's format.
-
# If not, return nil.
-
# * Determine if the file indicated by the URI actually exists and is readable.
-
# If not, return nil.
-
# * Read the file and place the contents in a {Sass::Engine}.
-
# Return that engine.
-
#
-
# If this importer's format allows for file extensions,
-
# it should treat them the same way as the default {Filesystem} importer.
-
# If the URI explicitly has a `.sass` or `.scss` filename,
-
# the importer should look for that exact file
-
# and import it as the syntax indicated.
-
# If it doesn't exist, the importer should return nil.
-
#
-
# If the URI doesn't have either of these extensions,
-
# the importer should look for files with the extensions.
-
# If no such files exist, it should return nil.
-
#
-
# The {Sass::Engine} to be returned should be passed `options`,
-
# with a few modifications. `:syntax` should be set appropriately,
-
# `:filename` should be set to `uri`,
-
# and `:importer` should be set to this importer.
-
#
-
# @param uri [String] The URI to import.
-
# @param options [{Symbol => Object}] Options for the Sass file
-
# containing the `@import` that's currently being resolved.
-
# This is safe for subclasses to modify destructively.
-
# Callers should only pass in a value they don't mind being destructively modified.
-
# @return [Sass::Engine, nil] An Engine containing the imported file,
-
# or nil if it couldn't be found or was in the wrong format.
-
1
def find(uri, options)
-
Sass::Util.abstract(self)
-
end
-
-
# Returns the time the given Sass file was last modified.
-
#
-
# If the given file has been deleted or the time can't be accessed
-
# for some other reason, this should return nil.
-
#
-
# @param uri [String] The URI of the file to check.
-
# Comes from a `:filename` option set on an engine returned by this importer.
-
# @param options [{Symbol => Objet}] Options for the Sass file
-
# containing the `@import` currently being checked.
-
# @return [Time, nil]
-
1
def mtime(uri, options)
-
Sass::Util.abstract(self)
-
end
-
-
# Get the cache key pair for the given Sass URI.
-
# The URI need not be checked for validity.
-
#
-
# The only strict requirement is that the returned pair of strings
-
# uniquely identify the file at the given URI.
-
# However, the first component generally corresponds roughly to the directory,
-
# and the second to the basename, of the URI.
-
#
-
# Note that keys must be unique *across importers*.
-
# Thus it's probably a good idea to include the importer name
-
# at the beginning of the first component.
-
#
-
# @param uri [String] A URI known to be valid for this importer.
-
# @param options [{Symbol => Object}] Options for the Sass file
-
# containing the `@import` currently being checked.
-
# @return [(String, String)] The key pair which uniquely identifies
-
# the file at the given URI.
-
1
def key(uri, options)
-
Sass::Util.abstract(self)
-
end
-
-
# A string representation of the importer.
-
# Should be overridden by subclasses.
-
#
-
# This is used to help debugging,
-
# and should usually just show the load path encapsulated by this importer.
-
#
-
# @return [String]
-
1
def to_s
-
Sass::Util.abstract(self)
-
end
-
end
-
end
-
end
-
-
-
1
require 'pathname'
-
-
1
module Sass
-
1
module Importers
-
# The default importer, used for any strings found in the load path.
-
# Simply loads Sass files from the filesystem using the default logic.
-
1
class Filesystem < Base
-
-
1
attr_accessor :root
-
-
# Creates a new filesystem importer that imports files relative to a given path.
-
#
-
# @param root [String] The root path.
-
# This importer will import files relative to this path.
-
1
def initialize(root)
-
@root = File.expand_path(root)
-
end
-
-
# @see Base#find_relative
-
1
def find_relative(name, base, options)
-
_find(File.dirname(base), name, options)
-
end
-
-
# @see Base#find
-
1
def find(name, options)
-
_find(@root, name, options)
-
end
-
-
# @see Base#mtime
-
1
def mtime(name, options)
-
file, s = find_real_file(@root, name)
-
File.mtime(file) if file
-
rescue Errno::ENOENT
-
nil
-
end
-
-
# @see Base#key
-
1
def key(name, options)
-
[self.class.name + ":" + File.dirname(File.expand_path(name)),
-
File.basename(name)]
-
end
-
-
# @see Base#to_s
-
1
def to_s
-
@root
-
end
-
-
1
def hash
-
@root.hash
-
end
-
-
1
def eql?(other)
-
root.eql?(other.root)
-
end
-
-
1
protected
-
-
# If a full uri is passed, this removes the root from it
-
# otherwise returns the name unchanged
-
1
def remove_root(name)
-
if name.index(@root + "/") == 0
-
name[(@root.length + 1)..-1]
-
else
-
name
-
end
-
end
-
-
# A hash from file extensions to the syntaxes for those extensions.
-
# The syntaxes must be `:sass` or `:scss`.
-
#
-
# This can be overridden by subclasses that want normal filesystem importing
-
# with unusual extensions.
-
#
-
# @return [{String => Symbol}]
-
1
def extensions
-
{'sass' => :sass, 'scss' => :scss}
-
end
-
-
# Given an `@import`ed path, returns an array of possible
-
# on-disk filenames and their corresponding syntaxes for that path.
-
#
-
# @param name [String] The filename.
-
# @return [Array(String, Symbol)] An array of pairs.
-
# The first element of each pair is a filename to look for;
-
# the second element is the syntax that file would be in (`:sass` or `:scss`).
-
1
def possible_files(name)
-
name = escape_glob_characters(name)
-
dirname, basename, extname = split(name)
-
sorted_exts = extensions.sort
-
syntax = extensions[extname]
-
-
return [["#{dirname}/{_,}#{basename}.#{extensions.invert[syntax]}", syntax]] if syntax
-
sorted_exts.map {|ext, syn| ["#{dirname}/{_,}#{basename}.#{ext}", syn]}
-
end
-
-
1
def escape_glob_characters(name)
-
name.gsub(/[\*\[\]\{\}\?]/) do |char|
-
"\\#{char}"
-
end
-
end
-
-
1
REDUNDANT_DIRECTORY = %r{#{Regexp.escape(File::SEPARATOR)}\.#{Regexp.escape(File::SEPARATOR)}}
-
# Given a base directory and an `@import`ed name,
-
# finds an existant file that matches the name.
-
#
-
# @param dir [String] The directory relative to which to search.
-
# @param name [String] The filename to search for.
-
# @return [(String, Symbol)] A filename-syntax pair.
-
1
def find_real_file(dir, name)
-
for (f,s) in possible_files(remove_root(name))
-
path = (dir == "." || Pathname.new(f).absolute?) ? f : "#{dir}/#{f}"
-
if full_path = Dir[path].first
-
full_path.gsub!(REDUNDANT_DIRECTORY,File::SEPARATOR)
-
return full_path, s
-
end
-
end
-
nil
-
end
-
-
# Splits a filename into three parts, a directory part, a basename, and an extension
-
# Only the known extensions returned from the extensions method will be recognized as such.
-
1
def split(name)
-
extension = nil
-
dirname, basename = File.dirname(name), File.basename(name)
-
if basename =~ /^(.*)\.(#{extensions.keys.map{|e| Regexp.escape(e)}.join('|')})$/
-
basename = $1
-
extension = $2
-
end
-
[dirname, basename, extension]
-
end
-
-
1
private
-
-
1
def _find(dir, name, options)
-
full_filename, syntax = find_real_file(dir, name)
-
return unless full_filename && File.readable?(full_filename)
-
-
options[:syntax] = syntax
-
options[:filename] = full_filename
-
options[:importer] = self
-
Sass::Engine.new(File.read(full_filename), options)
-
end
-
-
1
def join(base, path)
-
Pathname.new(base).join(path).to_s
-
end
-
end
-
end
-
end
-
1
module Sass::Logger
-
-
end
-
-
1
require "sass/logger/log_level"
-
1
require "sass/logger/base"
-
-
1
module Sass
-
-
1
class << self
-
1
attr_accessor :logger
-
end
-
-
1
self.logger = Sass::Logger::Base.new
-
end
-
1
require 'sass/logger/log_level'
-
-
1
class Sass::Logger::Base
-
-
1
include Sass::Logger::LogLevel
-
-
1
attr_accessor :log_level
-
1
attr_accessor :disabled
-
-
1
log_level :trace
-
1
log_level :debug
-
1
log_level :info
-
1
log_level :warn
-
1
log_level :error
-
-
1
def initialize(log_level = :debug)
-
2
self.log_level = log_level
-
end
-
-
1
def logging_level?(level)
-
!disabled && self.class.log_level?(level, log_level)
-
end
-
-
1
def log(level, message)
-
self._log(level, message) if logging_level?(level)
-
end
-
-
1
def _log(level, message)
-
Kernel::warn(message)
-
end
-
-
end
-
1
module Sass
-
1
module Logger
-
1
module LogLevel
-
-
1
def self.included(base)
-
1
base.extend(ClassMethods)
-
end
-
-
1
module ClassMethods
-
1
def inherited(subclass)
-
1
subclass.log_levels = subclass.superclass.log_levels.dup
-
end
-
-
1
def log_levels
-
11
@log_levels ||= {}
-
end
-
-
1
def log_levels=(levels)
-
1
@log_levels = levels
-
end
-
-
1
def log_level?(level, min_level)
-
log_levels[level] >= log_levels[min_level]
-
end
-
-
1
def log_level(name, options = {})
-
5
if options[:prepend]
-
level = log_levels.values.min
-
level = level.nil? ? 0 : level - 1
-
else
-
5
level = log_levels.values.max
-
5
level = level.nil? ? 0 : level + 1
-
end
-
5
log_levels.update(name => level)
-
5
define_logger(name)
-
end
-
-
1
def define_logger(name, options = {})
-
class_eval %Q{
-
def #{name}(message)
-
#{options.fetch(:to, :log)}(#{name.inspect}, message)
-
end
-
5
}
-
end
-
end
-
-
end
-
end
-
end
-
# Rails 3.0.0.beta.2+, < 3.1
-
1
if defined?(ActiveSupport) && Sass::Util.has?(:public_method, ActiveSupport, :on_load) &&
-
!Sass::Util.ap_geq?('3.1.0.beta')
-
require 'sass/plugin/configuration'
-
ActiveSupport.on_load(:before_configuration) do
-
require 'sass'
-
require 'sass/plugin'
-
end
-
end
-
1
module Sass
-
# The root directory of the Sass source tree.
-
# This may be overridden by the package manager
-
# if the lib directory is separated from the main source tree.
-
# @api public
-
1
ROOT_DIR = File.expand_path(File.join(__FILE__, "../../.."))
-
end
-
1
require 'sass/script/node'
-
1
require 'sass/script/variable'
-
1
require 'sass/script/funcall'
-
1
require 'sass/script/operation'
-
1
require 'sass/script/literal'
-
1
require 'sass/script/parser'
-
-
1
module Sass
-
# SassScript is code that's embedded in Sass documents
-
# to allow for property values to be computed from variables.
-
#
-
# This module contains code that handles the parsing and evaluation of SassScript.
-
1
module Script
-
# The regular expression used to parse variables.
-
1
MATCH = /^\$(#{Sass::SCSS::RX::IDENT})\s*:\s*(.+?)(!(?i:default))?$/
-
-
# The regular expression used to validate variables without matching.
-
1
VALIDATE = /^\$#{Sass::SCSS::RX::IDENT}$/
-
-
# Parses a string of SassScript
-
#
-
# @param value [String] The SassScript
-
# @param line [Fixnum] The number of the line on which the SassScript appeared.
-
# Used for error reporting
-
# @param offset [Fixnum] The number of characters in on `line` that the SassScript started.
-
# Used for error reporting
-
# @param options [{Symbol => Object}] An options hash;
-
# see {file:SASS_REFERENCE.md#sass_options the Sass options documentation}
-
# @return [Script::Node] The root node of the parse tree
-
1
def self.parse(value, line, offset, options = {})
-
Parser.parse(value, line, offset, options)
-
rescue Sass::SyntaxError => e
-
e.message << ": #{value.inspect}." if e.message == "SassScript error"
-
e.modify_backtrace(:line => line, :filename => options[:filename])
-
raise e
-
end
-
-
end
-
end
-
1
require 'sass/script/literal'
-
-
1
module Sass::Script
-
# A SassScript object representing a boolean (true or false) value.
-
1
class Bool < Literal
-
# The Ruby value of the boolean.
-
#
-
# @return [Boolean]
-
1
attr_reader :value
-
1
alias_method :to_bool, :value
-
-
# @return [String] "true" or "false"
-
1
def to_s(opts = {})
-
@value.to_s
-
end
-
1
alias_method :to_sass, :to_s
-
end
-
end
-
1
require 'sass/script/literal'
-
-
1
module Sass::Script
-
# A SassScript object representing a CSS color.
-
#
-
# A color may be represented internally as RGBA, HSLA, or both.
-
# It's originally represented as whatever its input is;
-
# if it's created with RGB values, it's represented as RGBA,
-
# and if it's created with HSL values, it's represented as HSLA.
-
# Once a property is accessed that requires the other representation --
-
# for example, \{#red} for an HSL color --
-
# that component is calculated and cached.
-
#
-
# The alpha channel of a color is independent of its RGB or HSL representation.
-
# It's always stored, as 1 if nothing else is specified.
-
# If only the alpha channel is modified using \{#with},
-
# the cached RGB and HSL values are retained.
-
1
class Color < Literal
-
2
class << self; include Sass::Util; end
-
-
# A hash from color names to `[red, green, blue]` value arrays.
-
1
HTML4_COLORS = map_vals({
-
'black' => 0x000000,
-
'silver' => 0xc0c0c0,
-
'gray' => 0x808080,
-
'white' => 0xffffff,
-
'maroon' => 0x800000,
-
'red' => 0xff0000,
-
'purple' => 0x800080,
-
'fuchsia' => 0xff00ff,
-
'green' => 0x008000,
-
'lime' => 0x00ff00,
-
'olive' => 0x808000,
-
'yellow' => 0xffff00,
-
'navy' => 0x000080,
-
'blue' => 0x0000ff,
-
'teal' => 0x008080,
-
'aqua' => 0x00ffff
-
64
}) {|color| (0..2).map {|n| color >> (n << 3) & 0xff}.reverse}
-
# A hash from `[red, green, blue]` value arrays to color names.
-
17
HTML4_COLORS_REVERSE = map_hash(HTML4_COLORS) {|k, v| [v, k]}
-
-
# Constructs an RGB or HSL color object,
-
# optionally with an alpha channel.
-
#
-
# The RGB values must be between 0 and 255.
-
# The saturation and lightness values must be between 0 and 100.
-
# The alpha value must be between 0 and 1.
-
#
-
# @raise [Sass::SyntaxError] if any color value isn't in the specified range
-
#
-
# @overload initialize(attrs)
-
# The attributes are specified as a hash.
-
# This hash must contain either `:hue`, `:saturation`, and `:value` keys,
-
# or `:red`, `:green`, and `:blue` keys.
-
# It cannot contain both HSL and RGB keys.
-
# It may also optionally contain an `:alpha` key.
-
#
-
# @param attrs [{Symbol => Numeric}] A hash of color attributes to values
-
# @raise [ArgumentError] if not enough attributes are specified,
-
# or both RGB and HSL attributes are specified
-
#
-
# @overload initialize(rgba)
-
# The attributes are specified as an array.
-
# This overload only supports RGB or RGBA colors.
-
#
-
# @param rgba [Array<Numeric>] A three- or four-element array
-
# of the red, green, blue, and optionally alpha values (respectively)
-
# of the color
-
# @raise [ArgumentError] if not enough attributes are specified
-
1
def initialize(attrs, allow_both_rgb_and_hsl = false)
-
super(nil)
-
-
if attrs.is_a?(Array)
-
unless (3..4).include?(attrs.size)
-
raise ArgumentError.new("Color.new(array) expects a three- or four-element array")
-
end
-
-
red, green, blue = attrs[0...3].map {|c| c.to_i}
-
@attrs = {:red => red, :green => green, :blue => blue}
-
@attrs[:alpha] = attrs[3] ? attrs[3].to_f : 1
-
else
-
attrs = attrs.reject {|k, v| v.nil?}
-
hsl = [:hue, :saturation, :lightness] & attrs.keys
-
rgb = [:red, :green, :blue] & attrs.keys
-
if !allow_both_rgb_and_hsl && !hsl.empty? && !rgb.empty?
-
raise ArgumentError.new("Color.new(hash) may not have both HSL and RGB keys specified")
-
elsif hsl.empty? && rgb.empty?
-
raise ArgumentError.new("Color.new(hash) must have either HSL or RGB keys specified")
-
elsif !hsl.empty? && hsl.size != 3
-
raise ArgumentError.new("Color.new(hash) must have all three HSL values specified")
-
elsif !rgb.empty? && rgb.size != 3
-
raise ArgumentError.new("Color.new(hash) must have all three RGB values specified")
-
end
-
-
@attrs = attrs
-
@attrs[:hue] %= 360 if @attrs[:hue]
-
@attrs[:alpha] ||= 1
-
end
-
-
[:red, :green, :blue].each do |k|
-
next if @attrs[k].nil?
-
@attrs[k] = @attrs[k].to_i
-
next if (0..255).include?(@attrs[k])
-
raise ArgumentError.new("#{k.to_s.capitalize} value must be between 0 and 255")
-
end
-
-
[:saturation, :lightness].each do |k|
-
next if @attrs[k].nil?
-
@attrs[k] = 0 if @attrs[k] < 0.00001 && @attrs[k] > -0.00001
-
@attrs[k] = 100 if @attrs[k] - 100 < 0.00001 && @attrs[k] - 100 > -0.00001
-
next if (0..100).include?(@attrs[k])
-
raise ArgumentError.new("#{k.to_s.capitalize} must be between 0 and 100")
-
end
-
-
unless (0..1).include?(@attrs[:alpha])
-
raise ArgumentError.new("Alpha channel must be between 0 and 1")
-
end
-
end
-
-
# The red component of the color.
-
#
-
# @return [Fixnum]
-
1
def red
-
hsl_to_rgb!
-
@attrs[:red]
-
end
-
-
# The green component of the color.
-
#
-
# @return [Fixnum]
-
1
def green
-
hsl_to_rgb!
-
@attrs[:green]
-
end
-
-
# The blue component of the color.
-
#
-
# @return [Fixnum]
-
1
def blue
-
hsl_to_rgb!
-
@attrs[:blue]
-
end
-
-
# The hue component of the color.
-
#
-
# @return [Numeric]
-
1
def hue
-
rgb_to_hsl!
-
@attrs[:hue]
-
end
-
-
# The saturation component of the color.
-
#
-
# @return [Numeric]
-
1
def saturation
-
rgb_to_hsl!
-
@attrs[:saturation]
-
end
-
-
# The lightness component of the color.
-
#
-
# @return [Numeric]
-
1
def lightness
-
rgb_to_hsl!
-
@attrs[:lightness]
-
end
-
-
# The alpha channel (opacity) of the color.
-
# This is 1 unless otherwise defined.
-
#
-
# @return [Fixnum]
-
1
def alpha
-
@attrs[:alpha]
-
end
-
-
# Returns whether this color object is translucent;
-
# that is, whether the alpha channel is non-1.
-
#
-
# @return [Boolean]
-
1
def alpha?
-
alpha < 1
-
end
-
-
# Returns the red, green, and blue components of the color.
-
#
-
# @return [Array<Fixnum>] A frozen three-element array of the red, green, and blue
-
# values (respectively) of the color
-
1
def rgb
-
[red, green, blue].freeze
-
end
-
-
# Returns the hue, saturation, and lightness components of the color.
-
#
-
# @return [Array<Fixnum>] A frozen three-element array of the
-
# hue, saturation, and lightness values (respectively) of the color
-
1
def hsl
-
[hue, saturation, lightness].freeze
-
end
-
-
# The SassScript `==` operation.
-
# **Note that this returns a {Sass::Script::Bool} object,
-
# not a Ruby boolean**.
-
#
-
# @param other [Literal] The right-hand side of the operator
-
# @return [Bool] True if this literal is the same as the other,
-
# false otherwise
-
1
def eq(other)
-
Sass::Script::Bool.new(
-
other.is_a?(Color) && rgb == other.rgb && alpha == other.alpha)
-
end
-
-
# Returns a copy of this color with one or more channels changed.
-
# RGB or HSL colors may be changed, but not both at once.
-
#
-
# For example:
-
#
-
# Color.new([10, 20, 30]).with(:blue => 40)
-
# #=> rgb(10, 40, 30)
-
# Color.new([126, 126, 126]).with(:red => 0, :green => 255)
-
# #=> rgb(0, 255, 126)
-
# Color.new([255, 0, 127]).with(:saturation => 60)
-
# #=> rgb(204, 51, 127)
-
# Color.new([1, 2, 3]).with(:alpha => 0.4)
-
# #=> rgba(1, 2, 3, 0.4)
-
#
-
# @param attrs [{Symbol => Numeric}]
-
# A map of channel names (`:red`, `:green`, `:blue`,
-
# `:hue`, `:saturation`, `:lightness`, or `:alpha`) to values
-
# @return [Color] The new Color object
-
# @raise [ArgumentError] if both RGB and HSL keys are specified
-
1
def with(attrs)
-
attrs = attrs.reject {|k, v| v.nil?}
-
hsl = !([:hue, :saturation, :lightness] & attrs.keys).empty?
-
rgb = !([:red, :green, :blue] & attrs.keys).empty?
-
if hsl && rgb
-
raise ArgumentError.new("Cannot specify HSL and RGB values for a color at the same time")
-
end
-
-
if hsl
-
[:hue, :saturation, :lightness].each {|k| attrs[k] ||= send(k)}
-
elsif rgb
-
[:red, :green, :blue].each {|k| attrs[k] ||= send(k)}
-
else
-
# If we're just changing the alpha channel,
-
# keep all the HSL/RGB stuff we've calculated
-
attrs = @attrs.merge(attrs)
-
end
-
attrs[:alpha] ||= alpha
-
-
Color.new(attrs, :allow_both_rgb_and_hsl)
-
end
-
-
# The SassScript `+` operation.
-
# Its functionality depends on the type of its argument:
-
#
-
# {Number}
-
# : Adds the number to each of the RGB color channels.
-
#
-
# {Color}
-
# : Adds each of the RGB color channels together.
-
#
-
# {Literal}
-
# : See {Literal#plus}.
-
#
-
# @param other [Literal] The right-hand side of the operator
-
# @return [Color] The resulting color
-
# @raise [Sass::SyntaxError] if `other` is a number with units
-
1
def plus(other)
-
if other.is_a?(Sass::Script::Number) || other.is_a?(Sass::Script::Color)
-
piecewise(other, :+)
-
else
-
super
-
end
-
end
-
-
# The SassScript `-` operation.
-
# Its functionality depends on the type of its argument:
-
#
-
# {Number}
-
# : Subtracts the number from each of the RGB color channels.
-
#
-
# {Color}
-
# : Subtracts each of the other color's RGB color channels from this color's.
-
#
-
# {Literal}
-
# : See {Literal#minus}.
-
#
-
# @param other [Literal] The right-hand side of the operator
-
# @return [Color] The resulting color
-
# @raise [Sass::SyntaxError] if `other` is a number with units
-
1
def minus(other)
-
if other.is_a?(Sass::Script::Number) || other.is_a?(Sass::Script::Color)
-
piecewise(other, :-)
-
else
-
super
-
end
-
end
-
-
# The SassScript `*` operation.
-
# Its functionality depends on the type of its argument:
-
#
-
# {Number}
-
# : Multiplies the number by each of the RGB color channels.
-
#
-
# {Color}
-
# : Multiplies each of the RGB color channels together.
-
#
-
# @param other [Number, Color] The right-hand side of the operator
-
# @return [Color] The resulting color
-
# @raise [Sass::SyntaxError] if `other` is a number with units
-
1
def times(other)
-
if other.is_a?(Sass::Script::Number) || other.is_a?(Sass::Script::Color)
-
piecewise(other, :*)
-
else
-
raise NoMethodError.new(nil, :times)
-
end
-
end
-
-
# The SassScript `/` operation.
-
# Its functionality depends on the type of its argument:
-
#
-
# {Number}
-
# : Divides each of the RGB color channels by the number.
-
#
-
# {Color}
-
# : Divides each of this color's RGB color channels by the other color's.
-
#
-
# {Literal}
-
# : See {Literal#div}.
-
#
-
# @param other [Literal] The right-hand side of the operator
-
# @return [Color] The resulting color
-
# @raise [Sass::SyntaxError] if `other` is a number with units
-
1
def div(other)
-
if other.is_a?(Sass::Script::Number) || other.is_a?(Sass::Script::Color)
-
piecewise(other, :/)
-
else
-
super
-
end
-
end
-
-
# The SassScript `%` operation.
-
# Its functionality depends on the type of its argument:
-
#
-
# {Number}
-
# : Takes each of the RGB color channels module the number.
-
#
-
# {Color}
-
# : Takes each of this color's RGB color channels modulo the other color's.
-
#
-
# @param other [Number, Color] The right-hand side of the operator
-
# @return [Color] The resulting color
-
# @raise [Sass::SyntaxError] if `other` is a number with units
-
1
def mod(other)
-
if other.is_a?(Sass::Script::Number) || other.is_a?(Sass::Script::Color)
-
piecewise(other, :%)
-
else
-
raise NoMethodError.new(nil, :mod)
-
end
-
end
-
-
# Returns a string representation of the color.
-
# This is usually the color's hex value,
-
# but if the color has a name that's used instead.
-
#
-
# @return [String] The string representation
-
1
def to_s(opts = {})
-
return rgba_str if alpha?
-
return smallest if options[:style] == :compressed
-
return HTML4_COLORS_REVERSE[rgb] if HTML4_COLORS_REVERSE[rgb]
-
hex_str
-
end
-
1
alias_method :to_sass, :to_s
-
-
# Returns a string representation of the color.
-
#
-
# @return [String] The hex value
-
1
def inspect
-
alpha? ? rgba_str : hex_str
-
end
-
-
1
private
-
-
1
def smallest
-
small_hex_str = hex_str.gsub(/^#(.)\1(.)\2(.)\3$/, '#\1\2\3')
-
return small_hex_str unless (color = HTML4_COLORS_REVERSE[rgb]) &&
-
color.size <= small_hex_str.size
-
return color
-
end
-
-
1
def rgba_str
-
split = options[:style] == :compressed ? ',' : ', '
-
"rgba(#{rgb.join(split)}#{split}#{Number.round(alpha)})"
-
end
-
-
1
def hex_str
-
red, green, blue = rgb.map { |num| num.to_s(16).rjust(2, '0') }
-
"##{red}#{green}#{blue}"
-
end
-
-
1
def piecewise(other, operation)
-
other_num = other.is_a? Number
-
if other_num && !other.unitless?
-
raise Sass::SyntaxError.new("Cannot add a number with units (#{other}) to a color (#{self}).")
-
end
-
-
result = []
-
for i in (0...3)
-
res = rgb[i].send(operation, other_num ? other.value : other.rgb[i])
-
result[i] = [ [res, 255].min, 0 ].max
-
end
-
-
if !other_num && other.alpha != alpha
-
raise Sass::SyntaxError.new("Alpha channels must be equal: #{self} #{operation} #{other}")
-
end
-
-
with(:red => result[0], :green => result[1], :blue => result[2])
-
end
-
-
1
def hsl_to_rgb!
-
return if @attrs[:red] && @attrs[:blue] && @attrs[:green]
-
-
h = @attrs[:hue] / 360.0
-
s = @attrs[:saturation] / 100.0
-
l = @attrs[:lightness] / 100.0
-
-
# Algorithm from the CSS3 spec: http://www.w3.org/TR/css3-color/#hsl-color.
-
m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s
-
m1 = l * 2 - m2
-
@attrs[:red], @attrs[:green], @attrs[:blue] = [
-
hue_to_rgb(m1, m2, h + 1.0/3),
-
hue_to_rgb(m1, m2, h),
-
hue_to_rgb(m1, m2, h - 1.0/3)
-
].map {|c| (c * 0xff).round}
-
end
-
-
1
def hue_to_rgb(m1, m2, h)
-
h += 1 if h < 0
-
h -= 1 if h > 1
-
return m1 + (m2 - m1) * h * 6 if h * 6 < 1
-
return m2 if h * 2 < 1
-
return m1 + (m2 - m1) * (2.0/3 - h) * 6 if h * 3 < 2
-
return m1
-
end
-
-
1
def rgb_to_hsl!
-
return if @attrs[:hue] && @attrs[:saturation] && @attrs[:lightness]
-
r, g, b = [:red, :green, :blue].map {|k| @attrs[k] / 255.0}
-
-
# Algorithm from http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV
-
max = [r, g, b].max
-
min = [r, g, b].min
-
d = max - min
-
-
h =
-
case max
-
when min; 0
-
when r; 60 * (g-b)/d
-
when g; 60 * (b-r)/d + 120
-
when b; 60 * (r-g)/d + 240
-
end
-
-
l = (max + min)/2.0
-
-
s =
-
if max == min
-
0
-
elsif l < 0.5
-
d/(2*l)
-
else
-
d/(2 - 2*l)
-
end
-
-
@attrs[:hue] = h % 360
-
@attrs[:saturation] = s * 100
-
@attrs[:lightness] = l * 100
-
end
-
end
-
end
-
1
module Sass
-
1
module Script
-
# This is a subclass of {Lexer} for use in parsing plain CSS properties.
-
#
-
# @see Sass::SCSS::CssParser
-
1
class CssLexer < Lexer
-
1
private
-
-
1
def token
-
important || super
-
end
-
-
1
def string(re, *args)
-
if re == :uri
-
return unless uri = scan(URI)
-
return [:string, Script::String.new(uri)]
-
end
-
-
return unless scan(STRING)
-
[:string, Script::String.new((@scanner[1] || @scanner[2]).gsub(/\\(['"])/, '\1'), :string)]
-
end
-
-
1
def important
-
return unless s = scan(IMPORTANT)
-
[:raw, s]
-
end
-
end
-
end
-
end
-
1
require 'sass/script'
-
1
require 'sass/script/css_lexer'
-
-
1
module Sass
-
1
module Script
-
# This is a subclass of {Parser} for use in parsing plain CSS properties.
-
#
-
# @see Sass::SCSS::CssParser
-
1
class CssParser < Parser
-
1
private
-
-
# @private
-
1
def lexer_class; CssLexer; end
-
-
# We need a production that only does /,
-
# since * and % aren't allowed in plain CSS
-
1
production :div, :unary_plus, :div
-
-
1
def string
-
return number unless tok = try_tok(:string)
-
return tok.value unless @lexer.peek && @lexer.peek.type == :begin_interpolation
-
end
-
-
# Short-circuit all the SassScript-only productions
-
1
alias_method :interpolation, :space
-
1
alias_method :or_expr, :div
-
1
alias_method :unary_div, :ident
-
1
alias_method :paren, :string
-
end
-
end
-
end
-
1
require 'sass/script/functions'
-
-
1
module Sass
-
1
module Script
-
# A SassScript parse node representing a function call.
-
#
-
# A function call either calls one of the functions in {Script::Functions},
-
# or if no function with the given name exists
-
# it returns a string representation of the function call.
-
1
class Funcall < Node
-
# The name of the function.
-
#
-
# @return [String]
-
1
attr_reader :name
-
-
# The arguments to the function.
-
#
-
# @return [Array<Script::Node>]
-
1
attr_reader :args
-
-
# The keyword arguments to the function.
-
#
-
# @return [{String => Script::Node}]
-
1
attr_reader :keywords
-
-
# @param name [String] See \{#name}
-
# @param args [Array<Script::Node>] See \{#args}
-
# @param keywords [{String => Script::Node}] See \{#keywords}
-
1
def initialize(name, args, keywords)
-
@name = name
-
@args = args
-
@keywords = keywords
-
super()
-
end
-
-
# @return [String] A string representation of the function call
-
1
def inspect
-
args = @args.map {|a| a.inspect}.join(', ')
-
keywords = Sass::Util.hash_to_a(@keywords).
-
map {|k, v| "$#{k}: #{v.inspect}"}.join(', ')
-
"#{name}(#{args}#{', ' unless args.empty? || keywords.empty?}#{keywords})"
-
end
-
-
# @see Node#to_sass
-
1
def to_sass(opts = {})
-
args = @args.map {|a| a.to_sass(opts)}.join(', ')
-
keywords = Sass::Util.hash_to_a(@keywords).
-
map {|k, v| "$#{dasherize(k, opts)}: #{v.to_sass(opts)}"}.join(', ')
-
"#{dasherize(name, opts)}(#{args}#{', ' unless args.empty? || keywords.empty?}#{keywords})"
-
end
-
-
# Returns the arguments to the function.
-
#
-
# @return [Array<Node>]
-
# @see Node#children
-
1
def children
-
@args + @keywords.values
-
end
-
-
# @see Node#deep_copy
-
1
def deep_copy
-
node = dup
-
node.instance_variable_set('@args', args.map {|a| a.deep_copy})
-
node.instance_variable_set('@keywords', Hash[keywords.map {|k, v| [k, v.deep_copy]}])
-
node
-
end
-
-
1
protected
-
-
# Evaluates the function call.
-
#
-
# @param environment [Sass::Environment] The environment in which to evaluate the SassScript
-
# @return [Literal] The SassScript object that is the value of the function call
-
# @raise [Sass::SyntaxError] if the function call raises an ArgumentError
-
1
def _perform(environment)
-
args = @args.map {|a| a.perform(environment)}
-
if fn = environment.function(@name)
-
keywords = Sass::Util.map_hash(@keywords) {|k, v| [k, v.perform(environment)]}
-
return perform_sass_fn(fn, args, keywords)
-
end
-
-
ruby_name = @name.tr('-', '_')
-
args = construct_ruby_args(ruby_name, args, environment)
-
-
unless Functions.callable?(ruby_name)
-
opts(to_literal(args))
-
else
-
opts(Functions::EvaluationContext.new(environment.options).send(ruby_name, *args))
-
end
-
rescue ArgumentError => e
-
# If this is a legitimate Ruby-raised argument error, re-raise it.
-
# Otherwise, it's an error in the user's stylesheet, so wrap it.
-
if e.message =~ /^wrong number of arguments \(\d+ for \d+\)/ &&
-
e.backtrace[0] !~ /:in `(block in )?#{ruby_name}'$/
-
raise e
-
end
-
raise Sass::SyntaxError.new("#{e.message} for `#{name}'")
-
end
-
-
# This method is factored out from `_perform` so that compass can override
-
# it with a cross-browser implementation for functions that require vendor prefixes
-
# in the generated css.
-
1
def to_literal(args)
-
Script::String.new("#{name}(#{args.join(', ')})")
-
end
-
-
1
private
-
-
1
def construct_ruby_args(name, args, environment)
-
unless signature = Functions.signature(name.to_sym, args.size, @keywords.size)
-
return args if keywords.empty?
-
raise Sass::SyntaxError.new("Function #{name} doesn't support keyword arguments")
-
end
-
keywords = Sass::Util.map_hash(@keywords) {|k, v| [k, v.perform(environment)]}
-
-
# If the user passes more non-keyword args than the function expects,
-
# but it does expect keyword args, Ruby's arg handling won't raise an error.
-
# Since we don't want to make functions think about this,
-
# we'll handle it for them here.
-
if signature.var_kwargs && !signature.var_args && args.size > signature.args.size
-
raise Sass::SyntaxError.new(
-
"#{args[signature.args.size].inspect} is not a keyword argument for `#{name}'")
-
elsif keywords.empty?
-
return args
-
end
-
-
args = args + signature.args[args.size..-1].map do |argname|
-
if keywords.has_key?(argname)
-
keywords.delete(argname)
-
else
-
raise Sass::SyntaxError.new("Function #{name} requires an argument named $#{argname}")
-
end
-
end
-
-
if keywords.size > 0
-
if signature.var_kwargs
-
args << keywords
-
else
-
raise Sass::SyntaxError.new("Function #{name} doesn't take an argument named $#{keywords.keys.sort.first}")
-
end
-
end
-
-
args
-
end
-
-
1
def perform_sass_fn(function, args, keywords)
-
# TODO: merge with mixin arg evaluation?
-
keywords.each do |name, value|
-
# TODO: Make this fast
-
unless function.args.find {|(var, default)| var.underscored_name == name}
-
raise Sass::SyntaxError.new("Function #{@name} doesn't have an argument named $#{name}")
-
end
-
end
-
-
if args.size > function.args.size
-
raise ArgumentError.new("Wrong number of arguments (#{args.size} for #{function.args.size})")
-
end
-
-
environment = function.args.zip(args).
-
inject(Sass::Environment.new(function.environment)) do |env, ((var, default), value)|
-
env.set_local_var(var.name,
-
value || keywords[var.underscored_name] || (default && default.perform(env)))
-
raise Sass::SyntaxError.new("Function #{@name} is missing parameter #{var.inspect}.") unless env.var(var.name)
-
env
-
end
-
-
val = catch :_sass_return do
-
function.tree.each {|c| Sass::Tree::Visitors::Perform.visit(c, environment)}
-
raise Sass::SyntaxError.new("Function #{@name} finished without @return")
-
end
-
val
-
end
-
end
-
end
-
end
-
1
module Sass::Script
-
# Methods in this module are accessible from the SassScript context.
-
# For example, you can write
-
#
-
# $color = hsl(120deg, 100%, 50%)
-
#
-
# and it will call {Sass::Script::Functions#hsl}.
-
#
-
# The following functions are provided:
-
#
-
# *Note: These functions are described in more detail below.*
-
#
-
# ## RGB Functions
-
#
-
# \{#rgb rgb($red, $green, $blue)}
-
# : Converts an `rgb(red, green, blue)` triplet into a color.
-
#
-
# \{#rgba rgba($red, $green, $blue, $alpha)}
-
# : Converts an `rgba(red, green, blue, alpha)` quadruplet into a color.
-
#
-
# \{#rgba rgba($color, $alpha)}
-
# : Adds an alpha layer to any color value.
-
#
-
# \{#red red($color)}
-
# : Gets the red component of a color.
-
#
-
# \{#green green($color)}
-
# : Gets the green component of a color.
-
#
-
# \{#blue blue($color)}
-
# : Gets the blue component of a color.
-
#
-
# \{#mix mix($color-1, $color-2, \[$weight\])}
-
# : Mixes two colors together.
-
#
-
# ## HSL Functions
-
#
-
# \{#hsl hsl($hue, $saturation, $lightness)}
-
# : Converts an `hsl(hue, saturation, lightness)` triplet into a color.
-
#
-
# \{#hsla hsla($hue, $saturation, $lightness, $alpha)}
-
# : Converts an `hsla(hue, saturation, lightness, alpha)` quadruplet into a color.
-
#
-
# \{#hue hue($color)}
-
# : Gets the hue component of a color.
-
#
-
# \{#saturation saturation($color)}
-
# : Gets the saturation component of a color.
-
#
-
# \{#lightness lightness($color)}
-
# : Gets the lightness component of a color.
-
#
-
# \{#adjust_hue adjust-hue($color, $degrees)}
-
# : Changes the hue of a color.
-
#
-
# \{#lighten lighten($color, $amount)}
-
# : Makes a color lighter.
-
#
-
# \{#darken darken($color, $amount)}
-
# : Makes a color darker.
-
#
-
# \{#saturate saturate($color, $amount)}
-
# : Makes a color more saturated.
-
#
-
# \{#desaturate desaturate($color, $amount)}
-
# : Makes a color less saturated.
-
#
-
# \{#grayscale grayscale($color)}
-
# : Converts a color to grayscale.
-
#
-
# \{#complement complement($color)}
-
# : Returns the complement of a color.
-
#
-
# \{#invert invert($color)}
-
# : Returns the inverse of a color.
-
#
-
# ## Opacity Functions
-
#
-
# \{#alpha alpha($color)} / \{#opacity opacity($color)}
-
# : Gets the alpha component (opacity) of a color.
-
#
-
# \{#rgba rgba($color, $alpha)}
-
# : Add or change an alpha layer for any color value.
-
#
-
# \{#opacify opacify($color, $amount)} / \{#fade_in fade-in($color, $amount)}
-
# : Makes a color more opaque.
-
#
-
# \{#transparentize transparentize($color, $amount)} / \{#fade_out fade-out($color, $amount)}
-
# : Makes a color more transparent.
-
#
-
# ## Other Color Functions
-
#
-
# \{#adjust_color adjust-color($color, \[$red\], \[$green\], \[$blue\], \[$hue\], \[$saturation\], \[$lightness\], \[$alpha\]}
-
# : Increase or decrease any of the components of a color.
-
#
-
# \{#scale_color scale-color($color, \[$red\], \[$green\], \[$blue\], \[$saturation\], \[$lightness\], \[$alpha\]}
-
# : Fluidly scale one or more components of a color.
-
#
-
# \{#change_color change-color($color, \[$red\], \[$green\], \[$blue\], \[$hue\], \[$saturation\], \[$lightness\], \[$alpha\]}
-
# : Changes one or more properties of a color.
-
#
-
# ## String Functions
-
#
-
# \{#unquote unquote($string)}
-
# : Removes the quotes from a string.
-
#
-
# \{#quote quote($string)}
-
# : Adds quotes to a string.
-
#
-
# ## Number Functions
-
#
-
# \{#percentage percentage($value)}
-
# : Converts a unitless number to a percentage.
-
#
-
# \{#round round($value)}
-
# : Rounds a number to the nearest whole number.
-
#
-
# \{#ceil ceil($value)}
-
# : Rounds a number up to the nearest whole number.
-
#
-
# \{#floor floor($value)}
-
# : Rounds a number down to the nearest whole number.
-
#
-
# \{#abs abs($value)}
-
# : Returns the absolute value of a number.
-
#
-
# ## List Functions {#list-functions}
-
#
-
# \{#length length($list)}
-
# : Returns the length of a list.
-
#
-
# \{#nth nth($list, $n)}
-
# : Returns a specific item in a list.
-
#
-
# \{#join join($list1, $list2, \[$separator\])}
-
# : Joins together two lists into one.
-
#
-
# ## Introspection Functions
-
#
-
# \{#type_of type-of($value)}
-
# : Returns the type of a value.
-
#
-
# \{#unit unit($number)}
-
# : Returns the units associated with a number.
-
#
-
# \{#unitless unitless($number)}
-
# : Returns whether a number has units or not.
-
#
-
# \{#comparable comparable($number-1, $number-2)}
-
# : Returns whether two numbers can be added or compared.
-
#
-
# ## Miscellaneous Functions
-
#
-
# \{#if if($condition, $if-true, $if-false)}
-
# : Returns one of two values, depending on whether or not a condition is true.
-
#
-
# ## Adding Custom Functions
-
#
-
# New Sass functions can be added by adding Ruby methods to this module.
-
# For example:
-
#
-
# module Sass::Script::Functions
-
# def reverse(string)
-
# assert_type string, :String
-
# Sass::Script::String.new(string.value.reverse)
-
# end
-
# declare :reverse, :args => [:string]
-
# end
-
#
-
# Calling {declare} tells Sass the argument names for your function.
-
# If omitted, the function will still work, but will not be able to accept keyword arguments.
-
# {declare} can also allow your function to take arbitrary keyword arguments.
-
#
-
# There are a few things to keep in mind when modifying this module.
-
# First of all, the arguments passed are {Sass::Script::Literal} objects.
-
# Literal objects are also expected to be returned.
-
# This means that Ruby values must be unwrapped and wrapped.
-
#
-
# Most Literal objects support the {Sass::Script::Literal#value value} accessor
-
# for getting their Ruby values.
-
# Color objects, though, must be accessed using {Sass::Script::Color#rgb rgb},
-
# {Sass::Script::Color#red red}, {Sass::Script::Color#blue green}, or {Sass::Script::Color#blue blue}.
-
#
-
# Second, making Ruby functions accessible from Sass introduces the temptation
-
# to do things like database access within stylesheets.
-
# This is generally a bad idea;
-
# since Sass files are by default only compiled once,
-
# dynamic code is not a great fit.
-
#
-
# If you really, really need to compile Sass on each request,
-
# first make sure you have adequate caching set up.
-
# Then you can use {Sass::Engine} to render the code,
-
# using the {file:SASS_REFERENCE.md#custom-option `options` parameter}
-
# to pass in data that {EvaluationContext#options can be accessed}
-
# from your Sass functions.
-
#
-
# Within one of the functions in this module,
-
# methods of {EvaluationContext} can be used.
-
#
-
# ### Caveats
-
#
-
# When creating new {Literal} objects within functions,
-
# be aware that it's not safe to call {Literal#to_s #to_s}
-
# (or other methods that use the string representation)
-
# on those objects without first setting {Node#options= the #options attribute}.
-
1
module Functions
-
1
@signatures = {}
-
-
# A class representing a Sass function signature.
-
#
-
# @attr args [Array<Symbol>] The names of the arguments to the function.
-
# @attr var_args [Boolean] Whether the function takes a variable number of arguments.
-
# @attr var_kwargs [Boolean] Whether the function takes an arbitrary set of keyword arguments.
-
1
Signature = Struct.new(:args, :var_args, :var_kwargs)
-
-
# Declare a Sass signature for a Ruby-defined function.
-
# This includes the names of the arguments,
-
# whether the function takes a variable number of arguments,
-
# and whether the function takes an arbitrary set of keyword arguments.
-
#
-
# It's not necessary to declare a signature for a function.
-
# However, without a signature it won't support keyword arguments.
-
#
-
# A single function can have multiple signatures declared
-
# as long as each one takes a different number of arguments.
-
# It's also possible to declare multiple signatures
-
# that all take the same number of arguments,
-
# but none of them but the first will be used
-
# unless the user uses keyword arguments.
-
#
-
# @param method_name [Symbol] The name of the method
-
# whose signature is being declared.
-
# @param args [Array<Symbol>] The names of the arguments for the function signature.
-
# @option options :var_args [Boolean] (false)
-
# Whether the function accepts a variable number of (unnamed) arguments
-
# in addition to the named arguments.
-
# @option options :var_kwargs [Boolean] (false)
-
# Whether the function accepts other keyword arguments
-
# in addition to those in `:args`.
-
# If this is true, the Ruby function will be passed a hash from strings
-
# to {Sass::Script::Literal}s as the last argument.
-
# In addition, if this is true and `:var_args` is not,
-
# Sass will ensure that the last argument passed is a hash.
-
#
-
# @example
-
# declare :rgba, [:hex, :alpha]
-
# declare :rgba, [:red, :green, :blue, :alpha]
-
# declare :accepts_anything, [], :var_args => true, :var_kwargs => true
-
# declare :some_func, [:foo, :bar, :baz], :var_kwargs => true
-
1
def self.declare(method_name, args, options = {})
-
49
@signatures[method_name] ||= []
-
@signatures[method_name] << Signature.new(
-
82
args.map {|s| s.to_s},
-
options[:var_args],
-
49
options[:var_kwargs])
-
end
-
-
# Determine the correct signature for the number of arguments
-
# passed in for a given function.
-
# If no signatures match, the first signature is returned for error messaging.
-
#
-
# @param method_name [Symbol] The name of the Ruby function to be called.
-
# @param arg_arity [Number] The number of unnamed arguments the function was passed.
-
# @param kwarg_arity [Number] The number of keyword arguments the function was passed.
-
#
-
# @return [{Symbol => Object}, nil]
-
# The signature options for the matching signature,
-
# or nil if no signatures are declared for this function. See {declare}.
-
1
def self.signature(method_name, arg_arity, kwarg_arity)
-
return unless @signatures[method_name]
-
@signatures[method_name].each do |signature|
-
return signature if signature.args.size == arg_arity + kwarg_arity
-
next unless signature.args.size < arg_arity + kwarg_arity
-
-
# We have enough args.
-
# Now we need to figure out which args are varargs
-
# and if the signature allows them.
-
t_arg_arity, t_kwarg_arity = arg_arity, kwarg_arity
-
if signature.args.size > t_arg_arity
-
# we transfer some kwargs arity to args arity
-
# if it does not have enough args -- assuming the names will work out.
-
t_kwarg_arity -= (signature.args.size - t_arg_arity)
-
t_arg_arity = signature.args.size
-
end
-
-
if ( t_arg_arity == signature.args.size || t_arg_arity > signature.args.size && signature.var_args ) &&
-
(t_kwarg_arity == 0 || t_kwarg_arity > 0 && signature.var_kwargs)
-
return signature
-
end
-
end
-
@signatures[method_name].first
-
end
-
-
# The context in which methods in {Script::Functions} are evaluated.
-
# That means that all instance methods of {EvaluationContext}
-
# are available to use in functions.
-
1
class EvaluationContext
-
1
include Functions
-
-
# The options hash for the {Sass::Engine} that is processing the function call
-
#
-
# @return [{Symbol => Object}]
-
1
attr_reader :options
-
-
# @param options [{Symbol => Object}] See \{#options}
-
1
def initialize(options)
-
@options = options
-
end
-
-
# Asserts that the type of a given SassScript value
-
# is the expected type (designated by a symbol).
-
#
-
# Valid types are `:Bool`, `:Color`, `:Number`, and `:String`.
-
# Note that `:String` will match both double-quoted strings
-
# and unquoted identifiers.
-
#
-
# @example
-
# assert_type value, :String
-
# assert_type value, :Number
-
# @param value [Sass::Script::Literal] A SassScript value
-
# @param type [Symbol] The name of the type the value is expected to be
-
# @param name [String, nil] The name of the argument.
-
1
def assert_type(value, type, name = nil)
-
return if value.is_a?(Sass::Script.const_get(type))
-
err = "#{value.inspect} is not a #{type.to_s.downcase}"
-
err = "$#{name}: " + err if name
-
raise ArgumentError.new(err)
-
end
-
end
-
-
1
class << self
-
# Returns whether user function with a given name exists.
-
#
-
# @param function_name [String]
-
# @return [Boolean]
-
1
alias_method :callable?, :public_method_defined?
-
-
1
private
-
1
def include(*args)
-
1
r = super
-
# We have to re-include ourselves into EvaluationContext to work around
-
# an icky Ruby restriction.
-
1
EvaluationContext.send :include, self
-
1
r
-
end
-
end
-
-
# Creates a {Color} object from red, green, and blue values.
-
#
-
# @param red [Number]
-
# A number between 0 and 255 inclusive,
-
# or between 0% and 100% inclusive
-
# @param green [Number]
-
# A number between 0 and 255 inclusive,
-
# or between 0% and 100% inclusive
-
# @param blue [Number]
-
# A number between 0 and 255 inclusive,
-
# or between 0% and 100% inclusive
-
# @see #rgba
-
# @return [Color]
-
1
def rgb(red, green, blue)
-
assert_type red, :Number
-
assert_type green, :Number
-
assert_type blue, :Number
-
-
Color.new([red, green, blue].map do |c|
-
v = c.value
-
if c.numerator_units == ["%"] && c.denominator_units.empty?
-
next v * 255 / 100.0 if (0..100).include?(v)
-
raise ArgumentError.new("Color value #{c} must be between 0% and 100% inclusive")
-
else
-
next v if (0..255).include?(v)
-
raise ArgumentError.new("Color value #{v} must be between 0 and 255 inclusive")
-
end
-
end)
-
end
-
1
declare :rgb, [:red, :green, :blue]
-
-
# @see #rgb
-
# @overload rgba(red, green, blue, alpha)
-
# Creates a {Color} object from red, green, and blue values,
-
# as well as an alpha channel indicating opacity.
-
#
-
# @param red [Number]
-
# A number between 0 and 255 inclusive
-
# @param green [Number]
-
# A number between 0 and 255 inclusive
-
# @param blue [Number]
-
# A number between 0 and 255 inclusive
-
# @param alpha [Number]
-
# A number between 0 and 1
-
# @return [Color]
-
#
-
# @overload rgba(color, alpha)
-
# Sets the opacity of a color.
-
#
-
# @example
-
# rgba(#102030, 0.5) => rgba(16, 32, 48, 0.5)
-
# rgba(blue, 0.2) => rgba(0, 0, 255, 0.2)
-
#
-
# @param color [Color]
-
# @param alpha [Number]
-
# A number between 0 and 1
-
# @return [Color]
-
1
def rgba(*args)
-
case args.size
-
when 2
-
color, alpha = args
-
-
assert_type color, :Color
-
assert_type alpha, :Number
-
-
unless (0..1).include?(alpha.value)
-
raise ArgumentError.new("Alpha channel #{alpha.value} must be between 0 and 1 inclusive")
-
end
-
-
color.with(:alpha => alpha.value)
-
when 4
-
red, green, blue, alpha = args
-
rgba(rgb(red, green, blue), alpha)
-
else
-
raise ArgumentError.new("wrong number of arguments (#{args.size} for 4)")
-
end
-
end
-
1
declare :rgba, [:red, :green, :blue, :alpha]
-
1
declare :rgba, [:color, :alpha]
-
-
# Creates a {Color} object from hue, saturation, and lightness.
-
# Uses the algorithm from the [CSS3 spec](http://www.w3.org/TR/css3-color/#hsl-color).
-
#
-
# @param hue [Number] The hue of the color.
-
# Should be between 0 and 360 degrees, inclusive
-
# @param saturation [Number] The saturation of the color.
-
# Must be between `0%` and `100%`, inclusive
-
# @param lightness [Number] The lightness of the color.
-
# Must be between `0%` and `100%`, inclusive
-
# @return [Color] The resulting color
-
# @see #hsla
-
# @raise [ArgumentError] if `saturation` or `lightness` are out of bounds
-
1
def hsl(hue, saturation, lightness)
-
hsla(hue, saturation, lightness, Number.new(1))
-
end
-
1
declare :hsl, [:hue, :saturation, :lightness]
-
-
# Creates a {Color} object from hue, saturation, and lightness,
-
# as well as an alpha channel indicating opacity.
-
# Uses the algorithm from the [CSS3 spec](http://www.w3.org/TR/css3-color/#hsl-color).
-
#
-
# @param hue [Number] The hue of the color.
-
# Should be between 0 and 360 degrees, inclusive
-
# @param saturation [Number] The saturation of the color.
-
# Must be between `0%` and `100%`, inclusive
-
# @param lightness [Number] The lightness of the color.
-
# Must be between `0%` and `100%`, inclusive
-
# @param alpha [Number] The opacity of the color.
-
# Must be between 0 and 1, inclusive
-
# @return [Color] The resulting color
-
# @see #hsl
-
# @raise [ArgumentError] if `saturation`, `lightness`, or `alpha` are out of bounds
-
1
def hsla(hue, saturation, lightness, alpha)
-
assert_type hue, :Number
-
assert_type saturation, :Number
-
assert_type lightness, :Number
-
assert_type alpha, :Number
-
-
unless (0..1).include?(alpha.value)
-
raise ArgumentError.new("Alpha channel #{alpha.value} must be between 0 and 1")
-
end
-
-
original_s = saturation
-
original_l = lightness
-
# This algorithm is from http://www.w3.org/TR/css3-color#hsl-color
-
h, s, l = [hue, saturation, lightness].map { |a| a.value }
-
raise ArgumentError.new("Saturation #{s} must be between 0% and 100%") unless (0..100).include?(s)
-
raise ArgumentError.new("Lightness #{l} must be between 0% and 100%") unless (0..100).include?(l)
-
-
Color.new(:hue => h, :saturation => s, :lightness => l, :alpha => alpha.value)
-
end
-
1
declare :hsla, [:hue, :saturation, :lightness, :alpha]
-
-
# Returns the red component of a color.
-
#
-
# @param color [Color]
-
# @return [Number]
-
# @raise [ArgumentError] If `color` isn't a color
-
1
def red(color)
-
assert_type color, :Color
-
Sass::Script::Number.new(color.red)
-
end
-
1
declare :red, [:color]
-
-
# Returns the green component of a color.
-
#
-
# @param color [Color]
-
# @return [Number]
-
# @raise [ArgumentError] If `color` isn't a color
-
1
def green(color)
-
assert_type color, :Color
-
Sass::Script::Number.new(color.green)
-
end
-
1
declare :green, [:color]
-
-
# Returns the blue component of a color.
-
#
-
# @param color [Color]
-
# @return [Number]
-
# @raise [ArgumentError] If `color` isn't a color
-
1
def blue(color)
-
assert_type color, :Color
-
Sass::Script::Number.new(color.blue)
-
end
-
1
declare :blue, [:color]
-
-
# Returns the hue component of a color.
-
#
-
# See [the CSS3 HSL specification](http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV).
-
#
-
# Calculated from RGB where necessary via [this algorithm](http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV).
-
#
-
# @param color [Color]
-
# @return [Number] between 0deg and 360deg
-
# @see #adjust_hue
-
# @raise [ArgumentError] if `color` isn't a color
-
1
def hue(color)
-
assert_type color, :Color
-
Sass::Script::Number.new(color.hue, ["deg"])
-
end
-
1
declare :hue, [:color]
-
-
# Returns the saturation component of a color.
-
#
-
# See [the CSS3 HSL specification](http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV).
-
#
-
# Calculated from RGB where necessary via [this algorithm](http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV).
-
#
-
# @param color [Color]
-
# @return [Number] between 0% and 100%
-
# @see #saturate
-
# @see #desaturate
-
# @raise [ArgumentError] if `color` isn't a color
-
1
def saturation(color)
-
assert_type color, :Color
-
Sass::Script::Number.new(color.saturation, ["%"])
-
end
-
1
declare :saturation, [:color]
-
-
# Returns the hue component of a color.
-
#
-
# See [the CSS3 HSL specification](http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV).
-
#
-
# Calculated from RGB where necessary via [this algorithm](http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV).
-
#
-
# @param color [Color]
-
# @return [Number] between 0% and 100%
-
# @see #lighten
-
# @see #darken
-
# @raise [ArgumentError] if `color` isn't a color
-
1
def lightness(color)
-
assert_type color, :Color
-
Sass::Script::Number.new(color.lightness, ["%"])
-
end
-
1
declare :lightness, [:color]
-
-
# Returns the alpha component (opacity) of a color.
-
# This is 1 unless otherwise specified.
-
#
-
# This function also supports the proprietary Microsoft
-
# `alpha(opacity=20)` syntax.
-
#
-
# @overload def alpha(color)
-
# @param color [Color]
-
# @return [Number]
-
# @see #opacify
-
# @see #transparentize
-
# @raise [ArgumentError] If `color` isn't a color
-
1
def alpha(*args)
-
if args.all? do |a|
-
a.is_a?(Sass::Script::String) && a.type == :identifier &&
-
a.value =~ /^[a-zA-Z]+\s*=/
-
end
-
# Support the proprietary MS alpha() function
-
return Sass::Script::String.new("alpha(#{args.map {|a| a.to_s}.join(", ")})")
-
end
-
-
opacity(*args)
-
end
-
1
declare :alpha, [:color]
-
-
# Returns the alpha component (opacity) of a color.
-
# This is 1 unless otherwise specified.
-
#
-
# @param color [Color]
-
# @return [Number]
-
# @see #opacify
-
# @see #transparentize
-
# @raise [ArgumentError] If `color` isn't a color
-
1
def opacity(color)
-
assert_type color, :Color
-
Sass::Script::Number.new(color.alpha)
-
end
-
1
declare :opacity, [:color]
-
-
# Makes a color more opaque.
-
# Takes a color and an amount between 0 and 1,
-
# and returns a color with the opacity increased by that value.
-
#
-
# @example
-
# opacify(rgba(0, 0, 0, 0.5), 0.1) => rgba(0, 0, 0, 0.6)
-
# opacify(rgba(0, 0, 17, 0.8), 0.2) => #001
-
# @param color [Color]
-
# @param amount [Number]
-
# @return [Color]
-
# @see #transparentize
-
# @raise [ArgumentError] If `color` isn't a color,
-
# or `number` isn't a number between 0 and 1
-
1
def opacify(color, amount)
-
_adjust(color, amount, :alpha, 0..1, :+)
-
end
-
1
declare :opacify, [:color, :amount]
-
-
1
alias_method :fade_in, :opacify
-
1
declare :fade_in, [:color, :amount]
-
-
# Makes a color more transparent.
-
# Takes a color and an amount between 0 and 1,
-
# and returns a color with the opacity decreased by that value.
-
#
-
# @example
-
# transparentize(rgba(0, 0, 0, 0.5), 0.1) => rgba(0, 0, 0, 0.4)
-
# transparentize(rgba(0, 0, 0, 0.8), 0.2) => rgba(0, 0, 0, 0.6)
-
# @param color [Color]
-
# @param amount [Number]
-
# @return [Color]
-
# @see #opacify
-
# @raise [ArgumentError] If `color` isn't a color,
-
# or `number` isn't a number between 0 and 1
-
1
def transparentize(color, amount)
-
_adjust(color, amount, :alpha, 0..1, :-)
-
end
-
1
declare :transparentize, [:color, :amount]
-
-
1
alias_method :fade_out, :transparentize
-
1
declare :fade_out, [:color, :amount]
-
-
# Makes a color lighter.
-
# Takes a color and an amount between 0% and 100%,
-
# and returns a color with the lightness increased by that value.
-
#
-
# @example
-
# lighten(hsl(0, 0%, 0%), 30%) => hsl(0, 0, 30)
-
# lighten(#800, 20%) => #e00
-
# @param color [Color]
-
# @param amount [Number]
-
# @return [Color]
-
# @see #darken
-
# @raise [ArgumentError] If `color` isn't a color,
-
# or `number` isn't a number between 0% and 100%
-
1
def lighten(color, amount)
-
_adjust(color, amount, :lightness, 0..100, :+, "%")
-
end
-
1
declare :lighten, [:color, :amount]
-
-
# Makes a color darker.
-
# Takes a color and an amount between 0% and 100%,
-
# and returns a color with the lightness decreased by that value.
-
#
-
# @example
-
# darken(hsl(25, 100%, 80%), 30%) => hsl(25, 100%, 50%)
-
# darken(#800, 20%) => #200
-
# @param color [Color]
-
# @param amount [Number]
-
# @return [Color]
-
# @see #lighten
-
# @raise [ArgumentError] If `color` isn't a color,
-
# or `number` isn't a number between 0% and 100%
-
1
def darken(color, amount)
-
_adjust(color, amount, :lightness, 0..100, :-, "%")
-
end
-
1
declare :darken, [:color, :amount]
-
-
# Makes a color more saturated.
-
# Takes a color and an amount between 0% and 100%,
-
# and returns a color with the saturation increased by that value.
-
#
-
# @example
-
# saturate(hsl(120, 30%, 90%), 20%) => hsl(120, 50%, 90%)
-
# saturate(#855, 20%) => #9e3f3f
-
# @param color [Color]
-
# @param amount [Number]
-
# @return [Color]
-
# @see #desaturate
-
# @raise [ArgumentError] If `color` isn't a color,
-
# or `number` isn't a number between 0% and 100%
-
1
def saturate(color, amount)
-
_adjust(color, amount, :saturation, 0..100, :+, "%")
-
end
-
1
declare :saturate, [:color, :amount]
-
-
# Makes a color less saturated.
-
# Takes a color and an amount between 0% and 100%,
-
# and returns a color with the saturation decreased by that value.
-
#
-
# @example
-
# desaturate(hsl(120, 30%, 90%), 20%) => hsl(120, 10%, 90%)
-
# desaturate(#855, 20%) => #726b6b
-
# @param color [Color]
-
# @param amount [Number]
-
# @return [Color]
-
# @see #saturate
-
# @raise [ArgumentError] If `color` isn't a color,
-
# or `number` isn't a number between 0% and 100%
-
1
def desaturate(color, amount)
-
_adjust(color, amount, :saturation, 0..100, :-, "%")
-
end
-
1
declare :desaturate, [:color, :amount]
-
-
# Changes the hue of a color while retaining the lightness and saturation.
-
# Takes a color and a number of degrees (usually between -360deg and 360deg),
-
# and returns a color with the hue rotated by that value.
-
#
-
# @example
-
# adjust-hue(hsl(120, 30%, 90%), 60deg) => hsl(180, 30%, 90%)
-
# adjust-hue(hsl(120, 30%, 90%), 060deg) => hsl(60, 30%, 90%)
-
# adjust-hue(#811, 45deg) => #886a11
-
# @param color [Color]
-
# @param amount [Number]
-
# @return [Color]
-
# @raise [ArgumentError] If `color` isn't a color, or `number` isn't a number
-
1
def adjust_hue(color, degrees)
-
assert_type color, :Color
-
assert_type degrees, :Number
-
color.with(:hue => color.hue + degrees.value)
-
end
-
1
declare :adjust_hue, [:color, :degrees]
-
-
# Adjusts one or more properties of a color.
-
# This can change the red, green, blue, hue, saturation, value, and alpha properties.
-
# The properties are specified as keyword arguments,
-
# and are added to or subtracted from the color's current value for that property.
-
#
-
# `$red`, `$green`, and `$blue` properties should be between 0 and 255.
-
# `$saturation` and `$lightness` should be between 0% and 100%.
-
# `$alpha` should be between 0 and 1.
-
#
-
# All properties are optional.
-
# You can't specify both RGB properties (`$red`, `$green`, `$blue`)
-
# and HSL properties (`$hue`, `$saturation`, `$value`) at the same time.
-
#
-
# @example
-
# adjust-color(#102030, $blue: 5) => #102035
-
# adjust-color(#102030, $red: -5, $blue: 5) => #0b2035
-
# adjust-color(hsl(25, 100%, 80%), $lightness: -30%, $alpha: -0.4) => hsla(25, 100%, 50%, 0.6)
-
# @param color [Color]
-
# @param red [Number]
-
# @param green [Number]
-
# @param blue [Number]
-
# @param hue [Number]
-
# @param saturation [Number]
-
# @param lightness [Number]
-
# @param alpha [Number]
-
# @return [Color]
-
# @raise [ArgumentError] if `color` is not a color,
-
# if any keyword argument is not a number,
-
# if any keyword argument is not in the legal range,
-
# if an unexpected keyword argument is given,
-
# or if both HSL and RGB properties are given.
-
1
def adjust_color(color, kwargs)
-
assert_type color, :Color
-
with = Sass::Util.map_hash({
-
"red" => [-255..255, ""],
-
"green" => [-255..255, ""],
-
"blue" => [-255..255, ""],
-
"hue" => nil,
-
"saturation" => [-100..100, "%"],
-
"lightness" => [-100..100, "%"],
-
"alpha" => [-1..1, ""]
-
}) do |name, (range, units)|
-
-
next unless val = kwargs.delete(name)
-
assert_type val, :Number, name
-
if range && !range.include?(val.value)
-
raise ArgumentError.new("$#{name}: Amount #{val} must be between #{range.first}#{units} and #{range.last}#{units}")
-
end
-
adjusted = color.send(name) + val.value
-
adjusted = [0, Sass::Util.restrict(adjusted, range)].max if range
-
[name.to_sym, adjusted]
-
end
-
-
unless kwargs.empty?
-
name, val = kwargs.to_a.first
-
raise ArgumentError.new("Unknown argument $#{name} (#{val})")
-
end
-
-
color.with(with)
-
end
-
1
declare :adjust_color, [:color], :var_kwargs => true
-
-
# Scales one or more properties of a color by a percentage value.
-
# Unlike \{#adjust_color adjust-color}, which changes a color's properties by fixed amounts,
-
# \{#scale_color scale-color} fluidly changes them based on how high or low they already are.
-
# That means that lightening an already-light color with \{#scale_color scale-color}
-
# won't change the lightness much,
-
# but lightening a dark color by the same amount will change it more dramatically.
-
# This has the benefit of making `scale-color($color, ...)` have a similar effect
-
# regardless of what `$color` is.
-
#
-
# For example, the lightness of a color can be anywhere between 0 and 100.
-
# If `scale-color($color, $lightness: 40%)` is called, the resulting color's lightness
-
# will be 40% of the way between its original lightness and 100.
-
# If `scale-color($color, $lightness: -40%)` is called instead,
-
# the lightness will be 40% of the way between the original and 0.
-
#
-
# This can change the red, green, blue, saturation, value, and alpha properties.
-
# The properties are specified as keyword arguments.
-
# All arguments should be percentages between 0% and 100%.
-
#
-
# All properties are optional.
-
# You can't specify both RGB properties (`$red`, `$green`, `$blue`)
-
# and HSL properties (`$saturation`, `$value`) at the same time.
-
#
-
# @example
-
# scale-color(hsl(120, 70, 80), $lightness: 50%) => hsl(120, 70, 90)
-
# scale-color(rgb(200, 150, 170), $green: -40%, $blue: 70%) => rgb(200, 90, 229)
-
# scale-color(hsl(200, 70, 80), $saturation: -90%, $alpha: -30%) => hsla(200, 7, 80, 0.7)
-
# @param color [Color]
-
# @param red [Number]
-
# @param green [Number]
-
# @param blue [Number]
-
# @param saturation [Number]
-
# @param lightness [Number]
-
# @param alpha [Number]
-
# @return [Color]
-
# @raise [ArgumentError] if `color` is not a color,
-
# if any keyword argument is not a percentage between 0% and 100%,
-
# if an unexpected keyword argument is given,
-
# or if both HSL and RGB properties are given.
-
1
def scale_color(color, kwargs)
-
assert_type color, :Color
-
with = Sass::Util.map_hash({
-
"red" => 255,
-
"green" => 255,
-
"blue" => 255,
-
"saturation" => 100,
-
"lightness" => 100,
-
"alpha" => 1
-
}) do |name, max|
-
-
next unless val = kwargs.delete(name)
-
assert_type val, :Number, name
-
if !(val.numerator_units == ['%'] && val.denominator_units.empty?)
-
raise ArgumentError.new("$#{name}: Amount #{val} must be a % (e.g. #{val.value}%)")
-
elsif !(-100..100).include?(val.value)
-
raise ArgumentError.new("$#{name}: Amount #{val} must be between -100% and 100%")
-
end
-
-
current = color.send(name)
-
scale = val.value/100.0
-
diff = scale > 0 ? max - current : current
-
[name.to_sym, current + diff*scale]
-
end
-
-
unless kwargs.empty?
-
name, val = kwargs.to_a.first
-
raise ArgumentError.new("Unknown argument $#{name} (#{val})")
-
end
-
-
color.with(with)
-
end
-
1
declare :scale_color, [:color], :var_kwargs => true
-
-
# Changes one or more properties of a color.
-
# This can change the red, green, blue, hue, saturation, value, and alpha properties.
-
# The properties are specified as keyword arguments,
-
# and replace the color's current value for that property.
-
#
-
# `$red`, `$green`, and `$blue` properties should be between 0 and 255.
-
# `$saturation` and `$lightness` should be between 0% and 100%.
-
# `$alpha` should be between 0 and 1.
-
#
-
# All properties are optional.
-
# You can't specify both RGB properties (`$red`, `$green`, `$blue`)
-
# and HSL properties (`$hue`, `$saturation`, `$value`) at the same time.
-
#
-
# @example
-
# change-color(#102030, $blue: 5) => #102005
-
# change-color(#102030, $red: 120, $blue: 5) => #782005
-
# change-color(hsl(25, 100%, 80%), $lightness: 40%, $alpha: 0.8) => hsla(25, 100%, 40%, 0.8)
-
# @param color [Color]
-
# @param red [Number]
-
# @param green [Number]
-
# @param blue [Number]
-
# @param hue [Number]
-
# @param saturation [Number]
-
# @param lightness [Number]
-
# @param alpha [Number]
-
# @return [Color]
-
# @raise [ArgumentError] if `color` is not a color,
-
# if any keyword argument is not a number,
-
# if any keyword argument is not in the legal range,
-
# if an unexpected keyword argument is given,
-
# or if both HSL and RGB properties are given.
-
1
def change_color(color, kwargs)
-
assert_type color, :Color
-
with = Sass::Util.map_hash(%w[red green blue hue saturation lightness alpha]) do |name, max|
-
next unless val = kwargs.delete(name)
-
assert_type val, :Number, name
-
[name.to_sym, val.value]
-
end
-
-
unless kwargs.empty?
-
name, val = kwargs.to_a.first
-
raise ArgumentError.new("Unknown argument $#{name} (#{val})")
-
end
-
-
color.with(with)
-
end
-
1
declare :change_color, [:color], :var_kwargs => true
-
-
# Mixes together two colors.
-
# Specifically, takes the average of each of the RGB components,
-
# optionally weighted by the given percentage.
-
# The opacity of the colors is also considered when weighting the components.
-
#
-
# The weight specifies the amount of the first color that should be included
-
# in the returned color.
-
# The default, 50%, means that half the first color
-
# and half the second color should be used.
-
# 25% means that a quarter of the first color
-
# and three quarters of the second color should be used.
-
#
-
# @example
-
# mix(#f00, #00f) => #7f007f
-
# mix(#f00, #00f, 25%) => #3f00bf
-
# mix(rgba(255, 0, 0, 0.5), #00f) => rgba(63, 0, 191, 0.75)
-
# @overload mix(color1, color2, weight: 50%)
-
# @param color1 [Color]
-
# @param color2 [Color]
-
# @param weight [Number] between 0% and 100%
-
# @return [Color]
-
# @raise [ArgumentError] if `color1` or `color2` aren't colors,
-
# or `weight` isn't a number between 0% and 100%
-
1
def mix(color1, color2, weight = Number.new(50))
-
assert_type color1, :Color
-
assert_type color2, :Color
-
assert_type weight, :Number
-
-
unless (0..100).include?(weight.value)
-
raise ArgumentError.new("Weight #{weight} must be between 0% and 100%")
-
end
-
-
# This algorithm factors in both the user-provided weight
-
# and the difference between the alpha values of the two colors
-
# to decide how to perform the weighted average of the two RGB values.
-
#
-
# It works by first normalizing both parameters to be within [-1, 1],
-
# where 1 indicates "only use color1", -1 indicates "only use color 0",
-
# and all values in between indicated a proportionately weighted average.
-
#
-
# Once we have the normalized variables w and a,
-
# we apply the formula (w + a)/(1 + w*a)
-
# to get the combined weight (in [-1, 1]) of color1.
-
# This formula has two especially nice properties:
-
#
-
# * When either w or a are -1 or 1, the combined weight is also that number
-
# (cases where w * a == -1 are undefined, and handled as a special case).
-
#
-
# * When a is 0, the combined weight is w, and vice versa
-
#
-
# Finally, the weight of color1 is renormalized to be within [0, 1]
-
# and the weight of color2 is given by 1 minus the weight of color1.
-
p = (weight.value/100.0).to_f
-
w = p*2 - 1
-
a = color1.alpha - color2.alpha
-
-
w1 = (((w * a == -1) ? w : (w + a)/(1 + w*a)) + 1)/2.0
-
w2 = 1 - w1
-
-
rgb = color1.rgb.zip(color2.rgb).map {|v1, v2| v1*w1 + v2*w2}
-
alpha = color1.alpha*p + color2.alpha*(1-p)
-
Color.new(rgb + [alpha])
-
end
-
1
declare :mix, [:color_1, :color_2]
-
1
declare :mix, [:color_1, :color_2, :weight]
-
-
# @overload grayscale(color)
-
# Converts a color to grayscale.
-
# This is identical to `desaturate(color, 100%)`.
-
#
-
# @param color [Color]
-
# @return [Color]
-
# @raise [ArgumentError] if `color` isn't a color
-
# @see #desaturate
-
# @overload grayscale(number)
-
# Returns an unquoted string `grayscale(number)`, as though the function
-
# were not defined. This is for the `grayscale` function used in
-
# `-webkit-filter`.
-
#
-
# @param number [Number]
-
# @return [Sass::Script::String]
-
1
def grayscale(color)
-
return Sass::Script::String.new("grayscale(#{color})") if color.is_a?(Sass::Script::Number)
-
desaturate color, Number.new(100)
-
end
-
1
declare :grayscale, [:color]
-
-
# Returns the complement of a color.
-
# This is identical to `adjust-hue(color, 180deg)`.
-
#
-
# @param color [Color]
-
# @return [Color]
-
# @raise [ArgumentError] if `color` isn't a color
-
# @see #adjust_hue #adjust-hue
-
1
def complement(color)
-
adjust_hue color, Number.new(180)
-
end
-
1
declare :complement, [:color]
-
-
# Returns the inverse (negative) of a color.
-
# The red, green, and blue values are inverted, while the opacity is left alone.
-
#
-
# @param color [Color]
-
# @return [Color]
-
# @raise [ArgumentError] if `color` isn't a color
-
1
def invert(color)
-
assert_type color, :Color
-
color.with(
-
:red => (255 - color.red),
-
:green => (255 - color.green),
-
:blue => (255 - color.blue))
-
end
-
-
# Removes quotes from a string if the string is quoted,
-
# or returns the same string if it's not.
-
#
-
# @param string [String]
-
# @return [String]
-
# @raise [ArgumentError] if `string` isn't a string
-
# @see #quote
-
# @example
-
# unquote("foo") => foo
-
# unquote(foo) => foo
-
1
def unquote(string)
-
if string.is_a?(Sass::Script::String)
-
Sass::Script::String.new(string.value, :identifier)
-
else
-
string
-
end
-
end
-
1
declare :unquote, [:string]
-
-
# Add quotes to a string if the string isn't quoted,
-
# or returns the same string if it is.
-
#
-
# @param string [String]
-
# @return [String]
-
# @raise [ArgumentError] if `string` isn't a string
-
# @see #unquote
-
# @example
-
# quote("foo") => "foo"
-
# quote(foo) => "foo"
-
1
def quote(string)
-
assert_type string, :String
-
Sass::Script::String.new(string.value, :string)
-
end
-
1
declare :quote, [:string]
-
-
# Inspects the type of the argument, returning it as an unquoted string.
-
#
-
# @example
-
# type-of(100px) => number
-
# type-of(asdf) => string
-
# type-of("asdf") => string
-
# type-of(true) => bool
-
# type-of(#fff) => color
-
# type-of(blue) => color
-
# @param value [Literal] The object to inspect
-
# @return [String] The unquoted string name of the literal's type
-
1
def type_of(value)
-
Sass::Script::String.new(value.class.name.gsub(/Sass::Script::/,'').downcase)
-
end
-
1
declare :type_of, [:value]
-
-
# Inspects the unit of the number, returning it as a quoted string.
-
# Complex units are sorted in alphabetical order by numerator and denominator.
-
#
-
# @example
-
# unit(100) => ""
-
# unit(100px) => "px"
-
# unit(3em) => "em"
-
# unit(10px * 5em) => "em*px"
-
# unit(10px * 5em / 30cm / 1rem) => "em*px/cm*rem"
-
# @param number [Literal] The number to inspect
-
# @return [String] The unit(s) of the number
-
# @raise [ArgumentError] if `number` isn't a number
-
1
def unit(number)
-
assert_type number, :Number
-
Sass::Script::String.new(number.unit_str, :string)
-
end
-
1
declare :unit, [:number]
-
-
# Inspects the unit of the number, returning a boolean indicating if it is unitless.
-
#
-
# @example
-
# unitless(100) => true
-
# unitless(100px) => false
-
# @param number [Literal] The number to inspect
-
# @return [Bool] Whether or not the number is unitless
-
# @raise [ArgumentError] if `number` isn't a number
-
1
def unitless(number)
-
assert_type number, :Number
-
Sass::Script::Bool.new(number.unitless?)
-
end
-
1
declare :unitless, [:number]
-
-
# Returns true if two numbers are similar enough to be added, subtracted, or compared.
-
#
-
# @example
-
# comparable(2px, 1px) => true
-
# comparable(100px, 3em) => false
-
# comparable(10cm, 3mm) => true
-
# @param number_1 [Number]
-
# @param number_2 [Number]
-
# @return [Bool] indicating if the numbers can be compared.
-
# @raise [ArgumentError] if `number_1` or `number_2` aren't numbers
-
1
def comparable(number_1, number_2)
-
assert_type number_1, :Number
-
assert_type number_2, :Number
-
Sass::Script::Bool.new(number_1.comparable_to?(number_2))
-
end
-
1
declare :comparable, [:number_1, :number_2]
-
-
# Converts a decimal number to a percentage.
-
#
-
# @example
-
# percentage(100px / 50px) => 200%
-
# @param value [Number] The decimal number to convert to a percentage
-
# @return [Number] The percentage
-
# @raise [ArgumentError] If `value` isn't a unitless number
-
1
def percentage(value)
-
unless value.is_a?(Sass::Script::Number) && value.unitless?
-
raise ArgumentError.new("#{value.inspect} is not a unitless number")
-
end
-
Sass::Script::Number.new(value.value * 100, ['%'])
-
end
-
1
declare :percentage, [:value]
-
-
# Rounds a number to the nearest whole number.
-
#
-
# @example
-
# round(10.4px) => 10px
-
# round(10.6px) => 11px
-
# @param value [Number] The number
-
# @return [Number] The rounded number
-
# @raise [ArgumentError] if `value` isn't a number
-
1
def round(value)
-
numeric_transformation(value) {|n| n.round}
-
end
-
1
declare :round, [:value]
-
-
# Rounds a number up to the nearest whole number.
-
#
-
# @example
-
# ciel(10.4px) => 11px
-
# ciel(10.6px) => 11px
-
# @param value [Number] The number
-
# @return [Number] The rounded number
-
# @raise [ArgumentError] if `value` isn't a number
-
1
def ceil(value)
-
numeric_transformation(value) {|n| n.ceil}
-
end
-
1
declare :ceil, [:value]
-
-
# Rounds down to the nearest whole number.
-
#
-
# @example
-
# floor(10.4px) => 10px
-
# floor(10.6px) => 10px
-
# @param value [Number] The number
-
# @return [Number] The rounded number
-
# @raise [ArgumentError] if `value` isn't a number
-
1
def floor(value)
-
numeric_transformation(value) {|n| n.floor}
-
end
-
1
declare :floor, [:value]
-
-
# Finds the absolute value of a number.
-
#
-
# @example
-
# abs(10px) => 10px
-
# abs(-10px) => 10px
-
# @param value [Number] The number
-
# @return [Number] The absolute value
-
# @raise [ArgumentError] if `value` isn't a number
-
1
def abs(value)
-
numeric_transformation(value) {|n| n.abs}
-
end
-
1
declare :abs, [:value]
-
-
# Return the length of a list.
-
#
-
# @example
-
# length(10px) => 1
-
# length(10px 20px 30px) => 3
-
# @param list [Literal] The list
-
# @return [Number] The length
-
1
def length(list)
-
Sass::Script::Number.new(list.to_a.size)
-
end
-
1
declare :length, [:list]
-
-
# Gets the nth item in a list.
-
#
-
# Note that unlike some languages, the first item in a Sass list is number 1,
-
# the second number 2, and so forth.
-
#
-
# @example
-
# nth(10px 20px 30px, 1) => 10px
-
# nth((Helvetica, Arial, sans-serif), 3) => sans-serif
-
# @param list [Literal] The list
-
# @param n [Number] The index into the list
-
# @return [Literal] The nth item in the list
-
# @raise [ArgumentError] If `n` isn't an integer between 1 and the list's length.
-
1
def nth(list, n)
-
assert_type n, :Number
-
if !n.int?
-
raise ArgumentError.new("List index #{n} must be an integer")
-
elsif n.to_i < 1
-
raise ArgumentError.new("List index #{n} must be greater than or equal to 1")
-
elsif list.to_a.size == 0
-
raise ArgumentError.new("List index is #{n} but list has no items")
-
elsif n.to_i > (size = list.to_a.size)
-
raise ArgumentError.new("List index is #{n} but list is only #{size} item#{'s' if size != 1} long")
-
end
-
-
list.to_a[n.to_i - 1]
-
end
-
1
declare :nth, [:list, :n]
-
-
# Joins together two lists into a new list.
-
#
-
# Unless the `$separator` argument is passed,
-
# if one list is comma-separated and one is space-separated,
-
# the first parameter's separator is used for the resulting list.
-
# If the lists have only one item each, spaces are used for the resulting list.
-
#
-
# @example
-
# join(10px 20px, 30px 40px) => 10px 20px 30px 40px
-
# join((blue, red), (#abc, #def)) => blue, red, #abc, #def
-
# join(10px, 20px) => 10px 20px
-
# join(10px, 20px, comma) => 10px, 20px
-
# join((blue, red), (#abc, #def), space) => blue red #abc #def
-
# @overload join(list1, list2, separator: auto)
-
# @param list1 [Literal] The first list to join
-
# @param list2 [Literal] The second list to join
-
# @param separator [String] How the list separator (comma or space) should be determined.
-
# If this is `comma` or `space`, that is always the separator;
-
# if this is `auto` (the default), the separator is determined as explained above.
-
1
def join(list1, list2, separator = Sass::Script::String.new("auto"))
-
assert_type separator, :String
-
unless %w[auto space comma].include?(separator.value)
-
raise ArgumentError.new("Separator name must be space, comma, or auto")
-
end
-
sep1 = list1.separator if list1.is_a?(Sass::Script::List) && !list1.value.empty?
-
sep2 = list2.separator if list2.is_a?(Sass::Script::List) && !list2.value.empty?
-
Sass::Script::List.new(
-
list1.to_a + list2.to_a,
-
if separator.value == 'auto'
-
sep1 || sep2 || :space
-
else
-
separator.value.to_sym
-
end)
-
end
-
1
declare :join, [:list1, :list2]
-
1
declare :join, [:list1, :list2, :separator]
-
-
# Appends a single value onto the end of a list.
-
#
-
# Unless the `$separator` argument is passed,
-
# if the list has only one item,
-
# the resulting list will be space-separated.
-
#
-
# @example
-
# append(10px 20px, 30px) => 10px 20px 30px
-
# append((blue, red), green) => blue, red, green
-
# append(10px 20px, 30px 40px) => 10px 20px (30px 40px)
-
# join(10px, 20px, comma) => 10px, 20px
-
# join((blue, red), green, space) => blue red green
-
# @overload join(list, val, separator: auto)
-
# @param list1 [Literal] The first list to join
-
# @param list2 [Literal] The second list to join
-
# @param separator [String] How the list separator (comma or space) should be determined.
-
# If this is `comma` or `space`, that is always the separator;
-
# if this is `auto` (the default), the separator is determined as explained above.
-
1
def append(list, val, separator = Sass::Script::String.new("auto"))
-
assert_type separator, :String
-
unless %w[auto space comma].include?(separator.value)
-
raise ArgumentError.new("Separator name must be space, comma, or auto")
-
end
-
sep = list.separator if list.is_a?(Sass::Script::List)
-
Sass::Script::List.new(
-
list.to_a + [val],
-
if separator.value == 'auto'
-
sep || :space
-
else
-
separator.value.to_sym
-
end)
-
end
-
1
declare :append, [:list, :val]
-
1
declare :append, [:list, :val, :separator]
-
-
# Combines several lists into a single comma separated list
-
# space separated lists.
-
#
-
# The length of the resulting list is the length of the
-
# shortest list.
-
#
-
# @example
-
# zip(1px 1px 3px, solid dashed solid, red green blue)
-
# => 1px solid red, 1px dashed green, 3px solid blue
-
1
def zip(*lists)
-
length = nil
-
values = []
-
lists.each do |list|
-
assert_type list, :List
-
values << list.value.dup
-
length = length.nil? ? list.value.length : [length, list.value.length].min
-
end
-
values.each do |value|
-
value.slice!(length)
-
end
-
new_list_value = values.first.zip(*values[1..-1])
-
List.new(new_list_value.map{|list| List.new(list, :space)}, :comma)
-
end
-
1
declare :zip, [], :var_args => true
-
-
-
# Returns the position of the given value within the given
-
# list. If not found, returns false.
-
#
-
# @example
-
# index(1px solid red, solid) => 2
-
# index(1px solid red, dashed) => false
-
1
def index(list, value)
-
assert_type list, :List
-
index = list.value.index {|e| e.eq(value).to_bool }
-
if index
-
Number.new(index + 1)
-
else
-
Bool.new(false)
-
end
-
end
-
1
declare :index, [:list, :value]
-
-
# Returns one of two values based on the truth value of the first argument.
-
#
-
# @example
-
# if(true, 1px, 2px) => 1px
-
# if(false, 1px, 2px) => 2px
-
# @param condition [Bool] Whether the first or second value will be returned.
-
# @param if_true [Literal] The value that will be returned if `$condition` is true.
-
# @param if_false [Literal] The value that will be returned if `$condition` is false.
-
1
def if(condition, if_true, if_false)
-
if condition.to_bool
-
if_true
-
else
-
if_false
-
end
-
end
-
1
declare :if, [:condition, :if_true, :if_false]
-
-
1
private
-
-
# This method implements the pattern of transforming a numeric value into
-
# another numeric value with the same units.
-
# It yields a number to a block to perform the operation and return a number
-
1
def numeric_transformation(value)
-
assert_type value, :Number
-
Sass::Script::Number.new(yield(value.value), value.numerator_units, value.denominator_units)
-
end
-
-
1
def _adjust(color, amount, attr, range, op, units = "")
-
assert_type color, :Color
-
assert_type amount, :Number
-
unless range.include?(amount.value)
-
raise ArgumentError.new("Amount #{amount} must be between #{range.first}#{units} and #{range.last}#{units}")
-
end
-
-
# TODO: is it worth restricting here,
-
# or should we do so in the Color constructor itself,
-
# and allow clipping in rgb() et al?
-
color.with(attr => Sass::Util.restrict(
-
color.send(attr).send(op, amount.value), range))
-
end
-
end
-
end
-
1
module Sass::Script
-
# A SassScript object representing `#{}` interpolation outside a string.
-
#
-
# @see StringInterpolation
-
1
class Interpolation < Node
-
# Interpolation in a property is of the form `before #{mid} after`.
-
#
-
# @param before [Node] The SassScript before the interpolation
-
# @param mid [Node] The SassScript within the interpolation
-
# @param after [Node] The SassScript after the interpolation
-
# @param wb [Boolean] Whether there was whitespace between `before` and `#{`
-
# @param wa [Boolean] Whether there was whitespace between `}` and `after`
-
# @param originally_text [Boolean]
-
# Whether the original format of the interpolation was plain text,
-
# not an interpolation.
-
# This is used when converting back to SassScript.
-
1
def initialize(before, mid, after, wb, wa, originally_text = false)
-
@before = before
-
@mid = mid
-
@after = after
-
@whitespace_before = wb
-
@whitespace_after = wa
-
@originally_text = originally_text
-
end
-
-
# @return [String] A human-readable s-expression representation of the interpolation
-
1
def inspect
-
"(interpolation #{@before.inspect} #{@mid.inspect} #{@after.inspect})"
-
end
-
-
# @see Node#to_sass
-
1
def to_sass(opts = {})
-
res = ""
-
res << @before.to_sass(opts) if @before
-
res << ' ' if @before && @whitespace_before
-
res << '#{' unless @originally_text
-
res << @mid.to_sass(opts)
-
res << '}' unless @originally_text
-
res << ' ' if @after && @whitespace_after
-
res << @after.to_sass(opts) if @after
-
res
-
end
-
-
# Returns the three components of the interpolation, `before`, `mid`, and `after`.
-
#
-
# @return [Array<Node>]
-
# @see #initialize
-
# @see Node#children
-
1
def children
-
[@before, @mid, @after].compact
-
end
-
-
# @see Node#deep_copy
-
1
def deep_copy
-
node = dup
-
node.instance_variable_set('@before', @before.deep_copy) if @before
-
node.instance_variable_set('@mid', @mid.deep_copy)
-
node.instance_variable_set('@after', @after.deep_copy) if @after
-
node
-
end
-
-
1
protected
-
-
# Evaluates the interpolation.
-
#
-
# @param environment [Sass::Environment] The environment in which to evaluate the SassScript
-
# @return [Sass::Script::String] The SassScript string that is the value of the interpolation
-
1
def _perform(environment)
-
res = ""
-
res << @before.perform(environment).to_s if @before
-
res << " " if @before && @whitespace_before
-
val = @mid.perform(environment)
-
res << (val.is_a?(Sass::Script::String) ? val.value : val.to_s)
-
res << " " if @after && @whitespace_after
-
res << @after.perform(environment).to_s if @after
-
opts(Sass::Script::String.new(res))
-
end
-
end
-
end
-
1
require 'sass/scss/rx'
-
-
1
module Sass
-
1
module Script
-
# The lexical analyzer for SassScript.
-
# It takes a raw string and converts it to individual tokens
-
# that are easier to parse.
-
1
class Lexer
-
1
include Sass::SCSS::RX
-
-
# A struct containing information about an individual token.
-
#
-
# `type`: \[`Symbol`\]
-
# : The type of token.
-
#
-
# `value`: \[`Object`\]
-
# : The Ruby object corresponding to the value of the token.
-
#
-
# `line`: \[`Fixnum`\]
-
# : The line of the source file on which the token appears.
-
#
-
# `offset`: \[`Fixnum`\]
-
# : The number of bytes into the line the SassScript token appeared.
-
#
-
# `pos`: \[`Fixnum`\]
-
# : The scanner position at which the SassScript token appeared.
-
1
Token = Struct.new(:type, :value, :line, :offset, :pos)
-
-
# The line number of the lexer's current position.
-
#
-
# @return [Fixnum]
-
1
attr_reader :line
-
-
# The number of bytes into the current line
-
# of the lexer's current position.
-
#
-
# @return [Fixnum]
-
1
attr_reader :offset
-
-
# A hash from operator strings to the corresponding token types.
-
1
OPERATORS = {
-
'+' => :plus,
-
'-' => :minus,
-
'*' => :times,
-
'/' => :div,
-
'%' => :mod,
-
'=' => :single_eq,
-
':' => :colon,
-
'(' => :lparen,
-
')' => :rparen,
-
',' => :comma,
-
'and' => :and,
-
'or' => :or,
-
'not' => :not,
-
'==' => :eq,
-
'!=' => :neq,
-
'>=' => :gte,
-
'<=' => :lte,
-
'>' => :gt,
-
'<' => :lt,
-
'#{' => :begin_interpolation,
-
'}' => :end_interpolation,
-
';' => :semicolon,
-
'{' => :lcurly,
-
}
-
-
24
OPERATORS_REVERSE = Sass::Util.map_hash(OPERATORS) {|k, v| [v, k]}
-
-
24
TOKEN_NAMES = Sass::Util.map_hash(OPERATORS_REVERSE) {|k, v| [k, v.inspect]}.merge({
-
:const => "variable (e.g. $foo)",
-
:ident => "identifier (e.g. middle)",
-
:bool => "boolean (e.g. true, false)",
-
})
-
-
# A list of operator strings ordered with longer names first
-
# so that `>` and `<` don't clobber `>=` and `<=`.
-
24
OP_NAMES = OPERATORS.keys.sort_by {|o| -o.size}
-
-
# A sub-list of {OP_NAMES} that only includes operators
-
# with identifier names.
-
24
IDENT_OP_NAMES = OP_NAMES.select {|k, v| k =~ /^\w+/}
-
-
# A hash of regular expressions that are used for tokenizing.
-
1
REGULAR_EXPRESSIONS = {
-
:whitespace => /\s+/,
-
:comment => COMMENT,
-
:single_line_comment => SINGLE_LINE_COMMENT,
-
:variable => /(\$)(#{IDENT})/,
-
:ident => /(#{IDENT})(\()?/,
-
:number => /(-)?(?:(\d*\.\d+)|(\d+))([a-zA-Z%]+)?/,
-
:color => HEXCOLOR,
-
:bool => /(true|false)\b/,
-
3
:ident_op => %r{(#{Regexp.union(*IDENT_OP_NAMES.map{|s| Regexp.new(Regexp.escape(s) + "(?!#{NMCHAR}|\Z)")})})},
-
:op => %r{(#{Regexp.union(*OP_NAMES)})},
-
}
-
-
1
class << self
-
1
private
-
1
def string_re(open, close)
-
4
/#{open}((?:\\.|\#(?!\{)|[^#{close}\\#])*)(#{close}|#\{)/
-
end
-
end
-
-
# A hash of regular expressions that are used for tokenizing strings.
-
#
-
# The key is a `[Symbol, Boolean]` pair.
-
# The symbol represents which style of quotation to use,
-
# while the boolean represents whether or not the string
-
# is following an interpolated segment.
-
1
STRING_REGULAR_EXPRESSIONS = {
-
[:double, false] => string_re('"', '"'),
-
[:single, false] => string_re("'", "'"),
-
[:double, true] => string_re('', '"'),
-
[:single, true] => string_re('', "'"),
-
[:uri, false] => /url\(#{W}(#{URLCHAR}*?)(#{W}\)|#\{)/,
-
[:uri, true] => /(#{URLCHAR}*?)(#{W}\)|#\{)/,
-
}
-
-
# @param str [String, StringScanner] The source text to lex
-
# @param line [Fixnum] The line on which the SassScript appears.
-
# Used for error reporting
-
# @param offset [Fixnum] The number of characters in on which the SassScript appears.
-
# Used for error reporting
-
# @param options [{Symbol => Object}] An options hash;
-
# see {file:SASS_REFERENCE.md#sass_options the Sass options documentation}
-
1
def initialize(str, line, offset, options)
-
@scanner = str.is_a?(StringScanner) ? str : Sass::Util::MultibyteStringScanner.new(str)
-
@line = line
-
@offset = offset
-
@options = options
-
@interpolation_stack = []
-
@prev = nil
-
end
-
-
# Moves the lexer forward one token.
-
#
-
# @return [Token] The token that was moved past
-
1
def next
-
@tok ||= read_token
-
@tok, tok = nil, @tok
-
@prev = tok
-
return tok
-
end
-
-
# Returns whether or not there's whitespace before the next token.
-
#
-
# @return [Boolean]
-
1
def whitespace?(tok = @tok)
-
if tok
-
@scanner.string[0...tok.pos] =~ /\s\Z/
-
else
-
@scanner.string[@scanner.pos, 1] =~ /^\s/ ||
-
@scanner.string[@scanner.pos - 1, 1] =~ /\s\Z/
-
end
-
end
-
-
# Returns the next token without moving the lexer forward.
-
#
-
# @return [Token] The next token
-
1
def peek
-
@tok ||= read_token
-
end
-
-
# Rewinds the underlying StringScanner
-
# to before the token returned by \{#peek}.
-
1
def unpeek!
-
@scanner.pos = @tok.pos if @tok
-
end
-
-
# @return [Boolean] Whether or not there's more source text to lex.
-
1
def done?
-
whitespace unless after_interpolation? && @interpolation_stack.last
-
@scanner.eos? && @tok.nil?
-
end
-
-
# @return [Boolean] Whether or not the last token lexed was `:end_interpolation`.
-
1
def after_interpolation?
-
@prev && @prev.type == :end_interpolation
-
end
-
-
# Raise an error to the effect that `name` was expected in the input stream
-
# and wasn't found.
-
#
-
# This calls \{#unpeek!} to rewind the scanner to immediately after
-
# the last returned token.
-
#
-
# @param name [String] The name of the entity that was expected but not found
-
# @raise [Sass::SyntaxError]
-
1
def expected!(name)
-
unpeek!
-
Sass::SCSS::Parser.expected(@scanner, name, @line)
-
end
-
-
# Records all non-comment text the lexer consumes within the block
-
# and returns it as a string.
-
#
-
# @yield A block in which text is recorded
-
# @return [String]
-
1
def str
-
old_pos = @tok ? @tok.pos : @scanner.pos
-
yield
-
new_pos = @tok ? @tok.pos : @scanner.pos
-
@scanner.string[old_pos...new_pos]
-
end
-
-
1
private
-
-
1
def read_token
-
return if done?
-
return unless value = token
-
type, val, size = value
-
size ||= @scanner.matched_size
-
-
val.line = @line if val.is_a?(Script::Node)
-
Token.new(type, val, @line,
-
current_position - size, @scanner.pos - size)
-
end
-
-
1
def whitespace
-
nil while scan(REGULAR_EXPRESSIONS[:whitespace]) ||
-
scan(REGULAR_EXPRESSIONS[:comment]) ||
-
scan(REGULAR_EXPRESSIONS[:single_line_comment])
-
end
-
-
1
def token
-
if after_interpolation? && (interp_type = @interpolation_stack.pop)
-
return string(interp_type, true)
-
end
-
-
variable || string(:double, false) || string(:single, false) || number ||
-
color || bool || string(:uri, false) || raw(UNICODERANGE) ||
-
special_fun || special_val || ident_op || ident || op
-
end
-
-
1
def variable
-
_variable(REGULAR_EXPRESSIONS[:variable])
-
end
-
-
1
def _variable(rx)
-
line = @line
-
offset = @offset
-
return unless scan(rx)
-
-
[:const, @scanner[2]]
-
end
-
-
1
def ident
-
return unless scan(REGULAR_EXPRESSIONS[:ident])
-
[@scanner[2] ? :funcall : :ident, @scanner[1]]
-
end
-
-
1
def string(re, open)
-
return unless scan(STRING_REGULAR_EXPRESSIONS[[re, open]])
-
if @scanner[2] == '#{' #'
-
@scanner.pos -= 2 # Don't actually consume the #{
-
@interpolation_stack << re
-
end
-
str =
-
if re == :uri
-
Script::String.new("#{'url(' unless open}#{@scanner[1]}#{')' unless @scanner[2] == '#{'}")
-
else
-
Script::String.new(@scanner[1].gsub(/\\(['"]|\#\{)/, '\1'), :string)
-
end
-
[:string, str]
-
end
-
-
1
def number
-
return unless scan(REGULAR_EXPRESSIONS[:number])
-
value = @scanner[2] ? @scanner[2].to_f : @scanner[3].to_i
-
value = -value if @scanner[1]
-
[:number, Script::Number.new(value, Array(@scanner[4]))]
-
end
-
-
1
def color
-
return unless s = scan(REGULAR_EXPRESSIONS[:color])
-
raise Sass::SyntaxError.new(<<MESSAGE.rstrip) unless s.size == 4 || s.size == 7
-
Colors must have either three or six digits: '#{s}'
-
MESSAGE
-
value = s.scan(/^#(..?)(..?)(..?)$/).first.
-
map {|num| num.ljust(2, num).to_i(16)}
-
[:color, Script::Color.new(value)]
-
end
-
-
1
def bool
-
return unless s = scan(REGULAR_EXPRESSIONS[:bool])
-
[:bool, Script::Bool.new(s == 'true')]
-
end
-
-
1
def special_fun
-
return unless str1 = scan(/((-[\w-]+-)?(calc|element)|expression|progid:[a-z\.]*)\(/i)
-
str2, _ = Sass::Shared.balance(@scanner, ?(, ?), 1)
-
c = str2.count("\n")
-
old_line = @line
-
old_offset = @offset
-
@line += c
-
@offset = (c == 0 ? @offset + str2.size : str2[/\n(.*)/, 1].size)
-
[:special_fun,
-
Sass::Util.merge_adjacent_strings(
-
[str1] + Sass::Engine.parse_interp(str2, old_line, old_offset, @options)),
-
str1.size + str2.size]
-
end
-
-
1
def special_val
-
return unless scan(/!important/i)
-
[:string, Script::String.new("!important")]
-
end
-
-
1
def ident_op
-
return unless op = scan(REGULAR_EXPRESSIONS[:ident_op])
-
[OPERATORS[op]]
-
end
-
-
1
def op
-
return unless op = scan(REGULAR_EXPRESSIONS[:op])
-
@interpolation_stack << nil if op == :begin_interpolation
-
[OPERATORS[op]]
-
end
-
-
1
def raw(rx)
-
return unless val = scan(rx)
-
[:raw, val]
-
end
-
-
1
def scan(re)
-
return unless str = @scanner.scan(re)
-
c = str.count("\n")
-
@line += c
-
@offset = (c == 0 ? @offset + str.size : str[/\n(.*)/, 1].size)
-
str
-
end
-
-
1
def current_position
-
@offset + 1
-
end
-
end
-
end
-
end
-
1
module Sass::Script
-
# A SassScript object representing a CSS list.
-
# This includes both comma-separated lists and space-separated lists.
-
1
class List < Literal
-
# The Ruby array containing the contents of the list.
-
#
-
# @return [Array<Literal>]
-
1
attr_reader :value
-
1
alias_method :children, :value
-
1
alias_method :to_a, :value
-
-
# The operator separating the values of the list.
-
# Either `:comma` or `:space`.
-
#
-
# @return [Symbol]
-
1
attr_reader :separator
-
-
# Creates a new list.
-
#
-
# @param value [Array<Literal>] See \{#value}
-
# @param separator [String] See \{#separator}
-
1
def initialize(value, separator)
-
super(value)
-
@separator = separator
-
end
-
-
# @see Node#deep_copy
-
1
def deep_copy
-
node = dup
-
node.instance_variable_set('@value', value.map {|c| c.deep_copy})
-
node
-
end
-
-
# @see Node#eq
-
1
def eq(other)
-
Sass::Script::Bool.new(
-
self.class == other.class && self.value == other.value &&
-
self.separator == other.separator)
-
end
-
-
# @see Node#to_s
-
1
def to_s(opts = {})
-
raise Sass::SyntaxError.new("() isn't a valid CSS value.") if value.empty?
-
return value.reject {|e| e.is_a?(List) && e.value.empty?}.map {|e| e.to_s(opts)}.join(sep_str)
-
end
-
-
# @see Node#to_sass
-
1
def to_sass(opts = {})
-
return "()" if value.empty?
-
precedence = Sass::Script::Parser.precedence_of(separator)
-
value.map do |v|
-
if v.is_a?(List) && Sass::Script::Parser.precedence_of(v.separator) <= precedence
-
"(#{v.to_sass(opts)})"
-
else
-
v.to_sass(opts)
-
end
-
end.join(sep_str(nil))
-
end
-
-
# @see Node#inspect
-
1
def inspect
-
"(#{to_sass})"
-
end
-
-
1
protected
-
-
# @see Node#_perform
-
1
def _perform(environment)
-
list = Sass::Script::List.new(
-
value.map {|e| e.perform(environment)},
-
separator)
-
list.options = self.options
-
list
-
end
-
-
1
private
-
-
1
def sep_str(opts = self.options)
-
return ' ' if separator == :space
-
return ',' if opts && opts[:style] == :compressed
-
return ', '
-
end
-
end
-
end
-
1
module Sass::Script
-
# The abstract superclass for SassScript objects.
-
#
-
# Many of these methods, especially the ones that correspond to SassScript operations,
-
# are designed to be overridden by subclasses which may change the semantics somewhat.
-
# The operations listed here are just the defaults.
-
1
class Literal < Node
-
1
require 'sass/script/string'
-
1
require 'sass/script/number'
-
1
require 'sass/script/color'
-
1
require 'sass/script/bool'
-
1
require 'sass/script/list'
-
-
# Returns the Ruby value of the literal.
-
# The type of this value varies based on the subclass.
-
#
-
# @return [Object]
-
1
attr_reader :value
-
-
# Creates a new literal.
-
#
-
# @param value [Object] The object for \{#value}
-
1
def initialize(value = nil)
-
@value = value
-
super()
-
end
-
-
# Returns an empty array.
-
#
-
# @return [Array<Node>] empty
-
# @see Node#children
-
1
def children
-
[]
-
end
-
-
# @see Node#deep_copy
-
1
def deep_copy
-
dup
-
end
-
-
# Returns the options hash for this node.
-
#
-
# @return [{Symbol => Object}]
-
# @raise [Sass::SyntaxError] if the options hash hasn't been set.
-
# This should only happen when the literal was created
-
# outside of the parser and \{#to\_s} was called on it
-
1
def options
-
opts = super
-
return opts if opts
-
raise Sass::SyntaxError.new(<<MSG)
-
The #options attribute is not set on this #{self.class}.
-
This error is probably occurring because #to_s was called
-
on this literal within a custom Sass function without first
-
setting the #option attribute.
-
MSG
-
end
-
-
# The SassScript `==` operation.
-
# **Note that this returns a {Sass::Script::Bool} object,
-
# not a Ruby boolean**.
-
#
-
# @param other [Literal] The right-hand side of the operator
-
# @return [Bool] True if this literal is the same as the other,
-
# false otherwise
-
1
def eq(other)
-
Sass::Script::Bool.new(self.class == other.class && self.value == other.value)
-
end
-
-
# The SassScript `!=` operation.
-
# **Note that this returns a {Sass::Script::Bool} object,
-
# not a Ruby boolean**.
-
#
-
# @param other [Literal] The right-hand side of the operator
-
# @return [Bool] False if this literal is the same as the other,
-
# true otherwise
-
1
def neq(other)
-
Sass::Script::Bool.new(!eq(other).to_bool)
-
end
-
-
# The SassScript `==` operation.
-
# **Note that this returns a {Sass::Script::Bool} object,
-
# not a Ruby boolean**.
-
#
-
# @param other [Literal] The right-hand side of the operator
-
# @return [Bool] True if this literal is the same as the other,
-
# false otherwise
-
1
def unary_not
-
Sass::Script::Bool.new(!to_bool)
-
end
-
-
# The SassScript default operation (e.g. `$a $b`, `"foo" "bar"`).
-
#
-
# @param other [Literal] The right-hand side of the operator
-
# @return [Script::String] A string containing both literals
-
# separated by a space
-
1
def space(other)
-
Sass::Script::String.new("#{self.to_s} #{other.to_s}")
-
end
-
-
# The SassScript `,` operation (e.g. `$a, $b`, `"foo", "bar"`).
-
#
-
# @param other [Literal] The right-hand side of the operator
-
# @return [Script::String] A string containing both literals
-
# separated by `", "`
-
1
def comma(other)
-
Sass::Script::String.new("#{self.to_s},#{' ' unless options[:style] == :compressed}#{other.to_s}")
-
end
-
-
# The SassScript `=` operation
-
# (used for proprietary MS syntax like `alpha(opacity=20)`).
-
#
-
# @param other [Literal] The right-hand side of the operator
-
# @return [Script::String] A string containing both literals
-
# separated by `"="`
-
1
def single_eq(other)
-
Sass::Script::String.new("#{self.to_s}=#{other.to_s}")
-
end
-
-
# The SassScript `+` operation.
-
#
-
# @param other [Literal] The right-hand side of the operator
-
# @return [Script::String] A string containing both literals
-
# without any separation
-
1
def plus(other)
-
if other.is_a?(Sass::Script::String)
-
return Sass::Script::String.new(self.to_s + other.value, other.type)
-
end
-
Sass::Script::String.new(self.to_s + other.to_s)
-
end
-
-
# The SassScript `-` operation.
-
#
-
# @param other [Literal] The right-hand side of the operator
-
# @return [Script::String] A string containing both literals
-
# separated by `"-"`
-
1
def minus(other)
-
Sass::Script::String.new("#{self.to_s}-#{other.to_s}")
-
end
-
-
# The SassScript `/` operation.
-
#
-
# @param other [Literal] The right-hand side of the operator
-
# @return [Script::String] A string containing both literals
-
# separated by `"/"`
-
1
def div(other)
-
Sass::Script::String.new("#{self.to_s}/#{other.to_s}")
-
end
-
-
# The SassScript unary `+` operation (e.g. `+$a`).
-
#
-
# @param other [Literal] The right-hand side of the operator
-
# @return [Script::String] A string containing the literal
-
# preceded by `"+"`
-
1
def unary_plus
-
Sass::Script::String.new("+#{self.to_s}")
-
end
-
-
# The SassScript unary `-` operation (e.g. `-$a`).
-
#
-
# @param other [Literal] The right-hand side of the operator
-
# @return [Script::String] A string containing the literal
-
# preceded by `"-"`
-
1
def unary_minus
-
Sass::Script::String.new("-#{self.to_s}")
-
end
-
-
# The SassScript unary `/` operation (e.g. `/$a`).
-
#
-
# @param other [Literal] The right-hand side of the operator
-
# @return [Script::String] A string containing the literal
-
# preceded by `"/"`
-
1
def unary_div
-
Sass::Script::String.new("/#{self.to_s}")
-
end
-
-
# @return [String] A readable representation of the literal
-
1
def inspect
-
value.inspect
-
end
-
-
# @return [Boolean] `true` (the Ruby boolean value)
-
1
def to_bool
-
true
-
end
-
-
# Compares this object with another.
-
#
-
# @param other [Object] The object to compare with
-
# @return [Boolean] Whether or not this literal is equivalent to `other`
-
1
def ==(other)
-
eq(other).to_bool
-
end
-
-
# @return [Fixnum] The integer value of this literal
-
# @raise [Sass::SyntaxError] if this literal isn't an integer
-
1
def to_i
-
raise Sass::SyntaxError.new("#{self.inspect} is not an integer.")
-
end
-
-
# @raise [Sass::SyntaxError] if this literal isn't an integer
-
1
def assert_int!; to_i; end
-
-
# Returns the value of this literal as a list.
-
# Single literals are considered the same as single-element lists.
-
#
-
# @return [Array<Literal>] The of this literal as a list
-
1
def to_a
-
[self]
-
end
-
-
# Returns the string representation of this literal
-
# as it would be output to the CSS document.
-
#
-
# @return [String]
-
1
def to_s(opts = {})
-
raise Sass::SyntaxError.new("[BUG] All subclasses of Sass::Literal must implement #to_s.")
-
end
-
1
alias_method :to_sass, :to_s
-
-
1
protected
-
-
# Evaluates the literal.
-
#
-
# @param environment [Sass::Environment] The environment in which to evaluate the SassScript
-
# @return [Literal] This literal
-
1
def _perform(environment)
-
self
-
end
-
end
-
end
-
1
module Sass::Script
-
# The abstract superclass for SassScript parse tree nodes.
-
#
-
# Use \{#perform} to evaluate a parse tree.
-
1
class Node
-
# The options hash for this node.
-
#
-
# @return [{Symbol => Object}]
-
1
attr_reader :options
-
-
# The line of the document on which this node appeared.
-
#
-
# @return [Fixnum]
-
1
attr_accessor :line
-
-
# Sets the options hash for this node,
-
# as well as for all child nodes.
-
# See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
-
#
-
# @param options [{Symbol => Object}] The options
-
1
def options=(options)
-
@options = options
-
children.each do |c|
-
if c.is_a? Hash
-
c.values.each {|v| v.options = options }
-
else
-
c.options = options
-
end
-
end
-
end
-
-
# Evaluates the node.
-
#
-
# \{#perform} shouldn't be overridden directly;
-
# instead, override \{#\_perform}.
-
#
-
# @param environment [Sass::Environment] The environment in which to evaluate the SassScript
-
# @return [Literal] The SassScript object that is the value of the SassScript
-
1
def perform(environment)
-
_perform(environment)
-
rescue Sass::SyntaxError => e
-
e.modify_backtrace(:line => line)
-
raise e
-
end
-
-
# Returns all child nodes of this node.
-
#
-
# @return [Array<Node>]
-
1
def children
-
Sass::Util.abstract(self)
-
end
-
-
# Returns the text of this SassScript expression.
-
#
-
# @return [String]
-
1
def to_sass(opts = {})
-
Sass::Util.abstract(self)
-
end
-
-
# Returns a deep clone of this node.
-
# The child nodes are cloned, but options are not.
-
#
-
# @return [Node]
-
1
def deep_copy
-
Sass::Util.abstract(self)
-
end
-
-
1
protected
-
-
# Converts underscores to dashes if the :dasherize option is set.
-
1
def dasherize(s, opts)
-
if opts[:dasherize]
-
s.gsub(/_/,'-')
-
else
-
s
-
end
-
end
-
-
# Evaluates this node.
-
# Note that all {Literal} objects created within this method
-
# should have their \{#options} attribute set, probably via \{#opts}.
-
#
-
# @param environment [Sass::Environment] The environment in which to evaluate the SassScript
-
# @return [Literal] The SassScript object that is the value of the SassScript
-
# @see #perform
-
1
def _perform(environment)
-
Sass::Util.abstract(self)
-
end
-
-
# Sets the \{#options} field on the given literal and returns it
-
#
-
# @param literal [Literal]
-
# @return [Literal]
-
1
def opts(literal)
-
literal.options = options
-
literal
-
end
-
end
-
end
-
1
require 'sass/script/literal'
-
-
1
module Sass::Script
-
# A SassScript object representing a number.
-
# SassScript numbers can have decimal values,
-
# and can also have units.
-
# For example, `12`, `1px`, and `10.45em`
-
# are all valid values.
-
#
-
# Numbers can also have more complex units, such as `1px*em/in`.
-
# These cannot be inputted directly in Sass code at the moment.
-
1
class Number < Literal
-
# The Ruby value of the number.
-
#
-
# @return [Numeric]
-
1
attr_reader :value
-
-
# A list of units in the numerator of the number.
-
# For example, `1px*em/in*cm` would return `["px", "em"]`
-
# @return [Array<String>]
-
1
attr_reader :numerator_units
-
-
# A list of units in the denominator of the number.
-
# For example, `1px*em/in*cm` would return `["in", "cm"]`
-
# @return [Array<String>]
-
1
attr_reader :denominator_units
-
-
# The original representation of this number.
-
# For example, although the result of `1px/2px` is `0.5`,
-
# the value of `#original` is `"1px/2px"`.
-
#
-
# This is only non-nil when the original value should be used as the CSS value,
-
# as in `font: 1px/2px`.
-
#
-
# @return [Boolean, nil]
-
1
attr_accessor :original
-
-
1
def self.precision
-
@precision ||= 3
-
end
-
-
# Sets the number of digits of precision
-
# For example, if this is `3`,
-
# `3.1415926` will be printed as `3.142`.
-
1
def self.precision=(digits)
-
@precision = digits.round
-
@precision_factor = 10.0**@precision
-
end
-
-
# the precision factor used in numeric output
-
# it is derived from the `precision` method.
-
1
def self.precision_factor
-
@precision_factor ||= 10.0**precision
-
end
-
-
# Handles the deprecation warning for the PRECISION constant
-
# This can be removed in 3.2.
-
1
def self.const_missing(const)
-
if const == :PRECISION
-
Sass::Util.sass_warn("Sass::Script::Number::PRECISION is deprecated and will be removed in a future release. Use Sass::Script::Number.precision_factor instead.")
-
const_set(:PRECISION, self.precision_factor)
-
else
-
super
-
end
-
end
-
-
# Used so we don't allocate two new arrays for each new number.
-
1
NO_UNITS = []
-
-
# @param value [Numeric] The value of the number
-
# @param numerator_units [Array<String>] See \{#numerator\_units}
-
# @param denominator_units [Array<String>] See \{#denominator\_units}
-
1
def initialize(value, numerator_units = NO_UNITS, denominator_units = NO_UNITS)
-
super(value)
-
@numerator_units = numerator_units
-
@denominator_units = denominator_units
-
normalize!
-
end
-
-
# The SassScript `+` operation.
-
# Its functionality depends on the type of its argument:
-
#
-
# {Number}
-
# : Adds the two numbers together, converting units if possible.
-
#
-
# {Color}
-
# : Adds this number to each of the RGB color channels.
-
#
-
# {Literal}
-
# : See {Literal#plus}.
-
#
-
# @param other [Literal] The right-hand side of the operator
-
# @return [Literal] The result of the operation
-
# @raise [Sass::UnitConversionError] if `other` is a number with incompatible units
-
1
def plus(other)
-
if other.is_a? Number
-
operate(other, :+)
-
elsif other.is_a?(Color)
-
other.plus(self)
-
else
-
super
-
end
-
end
-
-
# The SassScript binary `-` operation (e.g. `$a - $b`).
-
# Its functionality depends on the type of its argument:
-
#
-
# {Number}
-
# : Subtracts this number from the other, converting units if possible.
-
#
-
# {Literal}
-
# : See {Literal#minus}.
-
#
-
# @param other [Literal] The right-hand side of the operator
-
# @return [Literal] The result of the operation
-
# @raise [Sass::UnitConversionError] if `other` is a number with incompatible units
-
1
def minus(other)
-
if other.is_a? Number
-
operate(other, :-)
-
else
-
super
-
end
-
end
-
-
# The SassScript unary `+` operation (e.g. `+$a`).
-
#
-
# @return [Number] The value of this number
-
1
def unary_plus
-
self
-
end
-
-
# The SassScript unary `-` operation (e.g. `-$a`).
-
#
-
# @return [Number] The negative value of this number
-
1
def unary_minus
-
Number.new(-value, @numerator_units, @denominator_units)
-
end
-
-
# The SassScript `*` operation.
-
# Its functionality depends on the type of its argument:
-
#
-
# {Number}
-
# : Multiplies the two numbers together, converting units appropriately.
-
#
-
# {Color}
-
# : Multiplies each of the RGB color channels by this number.
-
#
-
# @param other [Number, Color] The right-hand side of the operator
-
# @return [Number, Color] The result of the operation
-
# @raise [NoMethodError] if `other` is an invalid type
-
1
def times(other)
-
if other.is_a? Number
-
operate(other, :*)
-
elsif other.is_a? Color
-
other.times(self)
-
else
-
raise NoMethodError.new(nil, :times)
-
end
-
end
-
-
# The SassScript `/` operation.
-
# Its functionality depends on the type of its argument:
-
#
-
# {Number}
-
# : Divides this number by the other, converting units appropriately.
-
#
-
# {Literal}
-
# : See {Literal#div}.
-
#
-
# @param other [Literal] The right-hand side of the operator
-
# @return [Literal] The result of the operation
-
1
def div(other)
-
if other.is_a? Number
-
res = operate(other, :/)
-
if self.original && other.original
-
res.original = "#{self.original}/#{other.original}"
-
end
-
res
-
else
-
super
-
end
-
end
-
-
# The SassScript `%` operation.
-
#
-
# @param other [Number] The right-hand side of the operator
-
# @return [Number] This number modulo the other
-
# @raise [NoMethodError] if `other` is an invalid type
-
# @raise [Sass::UnitConversionError] if `other` has any units
-
1
def mod(other)
-
if other.is_a?(Number)
-
unless other.unitless?
-
raise Sass::UnitConversionError.new("Cannot modulo by a number with units: #{other.inspect}.")
-
end
-
operate(other, :%)
-
else
-
raise NoMethodError.new(nil, :mod)
-
end
-
end
-
-
# The SassScript `==` operation.
-
#
-
# @param other [Literal] The right-hand side of the operator
-
# @return [Boolean] Whether this number is equal to the other object
-
1
def eq(other)
-
return Sass::Script::Bool.new(false) unless other.is_a?(Sass::Script::Number)
-
this = self
-
begin
-
if unitless?
-
this = this.coerce(other.numerator_units, other.denominator_units)
-
else
-
other = other.coerce(@numerator_units, @denominator_units)
-
end
-
rescue Sass::UnitConversionError
-
return Sass::Script::Bool.new(false)
-
end
-
-
Sass::Script::Bool.new(this.value == other.value)
-
end
-
-
# The SassScript `>` operation.
-
#
-
# @param other [Number] The right-hand side of the operator
-
# @return [Boolean] Whether this number is greater than the other
-
# @raise [NoMethodError] if `other` is an invalid type
-
1
def gt(other)
-
raise NoMethodError.new(nil, :gt) unless other.is_a?(Number)
-
operate(other, :>)
-
end
-
-
# The SassScript `>=` operation.
-
#
-
# @param other [Number] The right-hand side of the operator
-
# @return [Boolean] Whether this number is greater than or equal to the other
-
# @raise [NoMethodError] if `other` is an invalid type
-
1
def gte(other)
-
raise NoMethodError.new(nil, :gte) unless other.is_a?(Number)
-
operate(other, :>=)
-
end
-
-
# The SassScript `<` operation.
-
#
-
# @param other [Number] The right-hand side of the operator
-
# @return [Boolean] Whether this number is less than the other
-
# @raise [NoMethodError] if `other` is an invalid type
-
1
def lt(other)
-
raise NoMethodError.new(nil, :lt) unless other.is_a?(Number)
-
operate(other, :<)
-
end
-
-
# The SassScript `<=` operation.
-
#
-
# @param other [Number] The right-hand side of the operator
-
# @return [Boolean] Whether this number is less than or equal to the other
-
# @raise [NoMethodError] if `other` is an invalid type
-
1
def lte(other)
-
raise NoMethodError.new(nil, :lte) unless other.is_a?(Number)
-
operate(other, :<=)
-
end
-
-
# @return [String] The CSS representation of this number
-
# @raise [Sass::SyntaxError] if this number has units that can't be used in CSS
-
# (e.g. `px*in`)
-
1
def to_s(opts = {})
-
return original if original
-
raise Sass::SyntaxError.new("#{inspect} isn't a valid CSS value.") unless legal_units?
-
inspect
-
end
-
-
# Returns a readable representation of this number.
-
#
-
# This representation is valid CSS (and valid SassScript)
-
# as long as there is only one unit.
-
#
-
# @return [String] The representation
-
1
def inspect(opts = {})
-
value = self.class.round(self.value)
-
unitless? ? value.to_s : "#{value}#{unit_str}"
-
end
-
1
alias_method :to_sass, :inspect
-
-
# @return [Fixnum] The integer value of the number
-
# @raise [Sass::SyntaxError] if the number isn't an integer
-
1
def to_i
-
super unless int?
-
return value
-
end
-
-
# @return [Boolean] Whether or not this number is an integer.
-
1
def int?
-
value % 1 == 0.0
-
end
-
-
# @return [Boolean] Whether or not this number has no units.
-
1
def unitless?
-
@numerator_units.empty? && @denominator_units.empty?
-
end
-
-
# @return [Boolean] Whether or not this number has units that can be represented in CSS
-
# (that is, zero or one \{#numerator\_units}).
-
1
def legal_units?
-
(@numerator_units.empty? || @numerator_units.size == 1) && @denominator_units.empty?
-
end
-
-
# Returns this number converted to other units.
-
# The conversion takes into account the relationship between e.g. mm and cm,
-
# as well as between e.g. in and cm.
-
#
-
# If this number has no units, it will simply return itself
-
# with the given units.
-
#
-
# An incompatible coercion, e.g. between px and cm, will raise an error.
-
#
-
# @param num_units [Array<String>] The numerator units to coerce this number into.
-
# See {\#numerator\_units}
-
# @param den_units [Array<String>] The denominator units to coerce this number into.
-
# See {\#denominator\_units}
-
# @return [Number] The number with the new units
-
# @raise [Sass::UnitConversionError] if the given units are incompatible with the number's
-
# current units
-
1
def coerce(num_units, den_units)
-
Number.new(if unitless?
-
self.value
-
else
-
self.value * coercion_factor(@numerator_units, num_units) /
-
coercion_factor(@denominator_units, den_units)
-
end, num_units, den_units)
-
end
-
-
# @param other [Number] A number to decide if it can be compared with this number.
-
# @return [Boolean] Whether or not this number can be compared with the other.
-
1
def comparable_to?(other)
-
begin
-
operate(other, :+)
-
true
-
rescue Sass::UnitConversionError
-
false
-
end
-
end
-
-
# Returns a human readable representation of the units in this number.
-
# For complex units this takes the form of:
-
# numerator_unit1 * numerator_unit2 / denominator_unit1 * denominator_unit2
-
# @return [String] a string that represents the units in this number
-
1
def unit_str
-
rv = @numerator_units.sort.join("*")
-
if @denominator_units.any?
-
rv << "/"
-
rv << @denominator_units.sort.join("*")
-
end
-
rv
-
end
-
-
1
private
-
-
# @private
-
1
def self.round(num)
-
if num.is_a?(Float) && (num.infinite? || num.nan?)
-
num
-
elsif num % 1 == 0.0
-
num.to_i
-
else
-
((num * self.precision_factor).round / self.precision_factor).to_f
-
end
-
end
-
-
1
OPERATIONS = [:+, :-, :<=, :<, :>, :>=]
-
-
1
def operate(other, operation)
-
this = self
-
if OPERATIONS.include?(operation)
-
if unitless?
-
this = this.coerce(other.numerator_units, other.denominator_units)
-
else
-
other = other.coerce(@numerator_units, @denominator_units)
-
end
-
end
-
# avoid integer division
-
value = (:/ == operation) ? this.value.to_f : this.value
-
result = value.send(operation, other.value)
-
-
if result.is_a?(Numeric)
-
Number.new(result, *compute_units(this, other, operation))
-
else # Boolean op
-
Bool.new(result)
-
end
-
end
-
-
1
def coercion_factor(from_units, to_units)
-
# get a list of unmatched units
-
from_units, to_units = sans_common_units(from_units, to_units)
-
-
if from_units.size != to_units.size || !convertable?(from_units | to_units)
-
raise Sass::UnitConversionError.new("Incompatible units: '#{from_units.join('*')}' and '#{to_units.join('*')}'.")
-
end
-
-
from_units.zip(to_units).inject(1) {|m,p| m * conversion_factor(p[0], p[1]) }
-
end
-
-
1
def compute_units(this, other, operation)
-
case operation
-
when :*
-
[this.numerator_units + other.numerator_units, this.denominator_units + other.denominator_units]
-
when :/
-
[this.numerator_units + other.denominator_units, this.denominator_units + other.numerator_units]
-
else
-
[this.numerator_units, this.denominator_units]
-
end
-
end
-
-
1
def normalize!
-
return if unitless?
-
@numerator_units, @denominator_units = sans_common_units(@numerator_units, @denominator_units)
-
-
@denominator_units.each_with_index do |d, i|
-
if convertable?(d) && (u = @numerator_units.detect(&method(:convertable?)))
-
@value /= conversion_factor(d, u)
-
@denominator_units.delete_at(i)
-
@numerator_units.delete_at(@numerator_units.index(u))
-
end
-
end
-
end
-
-
# A hash of unit names to their index in the conversion table
-
1
CONVERTABLE_UNITS = {"in" => 0, "cm" => 1, "pc" => 2, "mm" => 3, "pt" => 4}
-
1
CONVERSION_TABLE = [[ 1, 2.54, 6, 25.4, 72 ], # in
-
[ nil, 1, 2.36220473, 10, 28.3464567], # cm
-
[ nil, nil, 1, 4.23333333, 12 ], # pc
-
[ nil, nil, nil, 1, 2.83464567], # mm
-
[ nil, nil, nil, nil, 1 ]] # pt
-
-
1
def conversion_factor(from_unit, to_unit)
-
res = CONVERSION_TABLE[CONVERTABLE_UNITS[from_unit]][CONVERTABLE_UNITS[to_unit]]
-
return 1.0 / conversion_factor(to_unit, from_unit) if res.nil?
-
res
-
end
-
-
1
def convertable?(units)
-
Array(units).all? {|u| CONVERTABLE_UNITS.include?(u)}
-
end
-
-
1
def sans_common_units(units1, units2)
-
units2 = units2.dup
-
# Can't just use -, because we want px*px to coerce properly to px*mm
-
return units1.map do |u|
-
next u unless j = units2.index(u)
-
units2.delete_at(j)
-
nil
-
end.compact, units2
-
end
-
end
-
end
-
1
require 'set'
-
1
require 'sass/script/string'
-
1
require 'sass/script/number'
-
1
require 'sass/script/color'
-
1
require 'sass/script/functions'
-
1
require 'sass/script/unary_operation'
-
1
require 'sass/script/interpolation'
-
1
require 'sass/script/string_interpolation'
-
-
1
module Sass::Script
-
# A SassScript parse node representing a binary operation,
-
# such as `$a + $b` or `"foo" + 1`.
-
1
class Operation < Node
-
1
attr_reader :operand1
-
1
attr_reader :operand2
-
1
attr_reader :operator
-
-
# @param operand1 [Script::Node] The parse-tree node
-
# for the right-hand side of the operator
-
# @param operand2 [Script::Node] The parse-tree node
-
# for the left-hand side of the operator
-
# @param operator [Symbol] The operator to perform.
-
# This should be one of the binary operator names in {Lexer::OPERATORS}
-
1
def initialize(operand1, operand2, operator)
-
@operand1 = operand1
-
@operand2 = operand2
-
@operator = operator
-
super()
-
end
-
-
# @return [String] A human-readable s-expression representation of the operation
-
1
def inspect
-
"(#{@operator.inspect} #{@operand1.inspect} #{@operand2.inspect})"
-
end
-
-
# @see Node#to_sass
-
1
def to_sass(opts = {})
-
pred = Sass::Script::Parser.precedence_of(@operator)
-
o1 = operand_to_sass @operand1, :left, opts
-
o2 = operand_to_sass @operand2, :right, opts
-
sep =
-
case @operator
-
when :comma; ", "
-
when :space; " "
-
else; " #{Lexer::OPERATORS_REVERSE[@operator]} "
-
end
-
"#{o1}#{sep}#{o2}"
-
end
-
-
# Returns the operands for this operation.
-
#
-
# @return [Array<Node>]
-
# @see Node#children
-
1
def children
-
[@operand1, @operand2]
-
end
-
-
# @see Node#deep_copy
-
1
def deep_copy
-
node = dup
-
node.instance_variable_set('@operand1', @operand1.deep_copy)
-
node.instance_variable_set('@operand2', @operand2.deep_copy)
-
node
-
end
-
-
1
protected
-
-
# Evaluates the operation.
-
#
-
# @param environment [Sass::Environment] The environment in which to evaluate the SassScript
-
# @return [Literal] The SassScript object that is the value of the operation
-
# @raise [Sass::SyntaxError] if the operation is undefined for the operands
-
1
def _perform(environment)
-
literal1 = @operand1.perform(environment)
-
-
# Special-case :and and :or to support short-circuiting.
-
if @operator == :and
-
return literal1.to_bool ? @operand2.perform(environment) : literal1
-
elsif @operator == :or
-
return literal1.to_bool ? literal1 : @operand2.perform(environment)
-
end
-
-
literal2 = @operand2.perform(environment)
-
-
begin
-
opts(literal1.send(@operator, literal2))
-
rescue NoMethodError => e
-
raise e unless e.name.to_s == @operator.to_s
-
raise Sass::SyntaxError.new("Undefined operation: \"#{literal1} #{@operator} #{literal2}\".")
-
end
-
end
-
-
1
private
-
-
1
def operand_to_sass(op, side, opts)
-
return "(#{op.to_sass(opts)})" if op.is_a?(List)
-
return op.to_sass(opts) unless op.is_a?(Operation)
-
-
pred = Sass::Script::Parser.precedence_of(@operator)
-
sub_pred = Sass::Script::Parser.precedence_of(op.operator)
-
assoc = Sass::Script::Parser.associative?(@operator)
-
return "(#{op.to_sass(opts)})" if sub_pred < pred ||
-
(side == :right && sub_pred == pred && !assoc)
-
op.to_sass(opts)
-
end
-
end
-
end
-
1
require 'sass/script/lexer'
-
-
1
module Sass
-
1
module Script
-
# The parser for SassScript.
-
# It parses a string of code into a tree of {Script::Node}s.
-
1
class Parser
-
# The line number of the parser's current position.
-
#
-
# @return [Fixnum]
-
1
def line
-
@lexer.line
-
end
-
-
# @param str [String, StringScanner] The source text to parse
-
# @param line [Fixnum] The line on which the SassScript appears.
-
# Used for error reporting
-
# @param offset [Fixnum] The number of characters in on which the SassScript appears.
-
# Used for error reporting
-
# @param options [{Symbol => Object}] An options hash;
-
# see {file:SASS_REFERENCE.md#sass_options the Sass options documentation}
-
1
def initialize(str, line, offset, options = {})
-
@options = options
-
@lexer = lexer_class.new(str, line, offset, options)
-
end
-
-
# Parses a SassScript expression within an interpolated segment (`#{}`).
-
# This means that it stops when it comes across an unmatched `}`,
-
# which signals the end of an interpolated segment,
-
# it returns rather than throwing an error.
-
#
-
# @return [Script::Node] The root node of the parse tree
-
# @raise [Sass::SyntaxError] if the expression isn't valid SassScript
-
1
def parse_interpolated
-
expr = assert_expr :expr
-
assert_tok :end_interpolation
-
expr.options = @options
-
expr
-
rescue Sass::SyntaxError => e
-
e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
-
raise e
-
end
-
-
# Parses a SassScript expression.
-
#
-
# @return [Script::Node] The root node of the parse tree
-
# @raise [Sass::SyntaxError] if the expression isn't valid SassScript
-
1
def parse
-
expr = assert_expr :expr
-
assert_done
-
expr.options = @options
-
expr
-
rescue Sass::SyntaxError => e
-
e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
-
raise e
-
end
-
-
# Parses a SassScript expression,
-
# ending it when it encounters one of the given identifier tokens.
-
#
-
# @param [#include?(String)] A set of strings that delimit the expression.
-
# @return [Script::Node] The root node of the parse tree
-
# @raise [Sass::SyntaxError] if the expression isn't valid SassScript
-
1
def parse_until(tokens)
-
@stop_at = tokens
-
expr = assert_expr :expr
-
assert_done
-
expr.options = @options
-
expr
-
rescue Sass::SyntaxError => e
-
e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
-
raise e
-
end
-
-
# Parses the argument list for a mixin include.
-
#
-
# @return [(Array<Script::Node>, {String => Script::Note})]
-
# The root nodes of the arguments.
-
# Keyword arguments are in a hash from names to values.
-
# @raise [Sass::SyntaxError] if the argument list isn't valid SassScript
-
1
def parse_mixin_include_arglist
-
args, keywords = [], {}
-
if try_tok(:lparen)
-
args, keywords = mixin_arglist || [[], {}]
-
assert_tok(:rparen)
-
end
-
assert_done
-
-
args.each {|a| a.options = @options}
-
keywords.each {|k, v| v.options = @options}
-
return args, keywords
-
rescue Sass::SyntaxError => e
-
e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
-
raise e
-
end
-
-
# Parses the argument list for a mixin definition.
-
#
-
# @return [Array<Script::Node>] The root nodes of the arguments.
-
# @raise [Sass::SyntaxError] if the argument list isn't valid SassScript
-
1
def parse_mixin_definition_arglist
-
args = defn_arglist!(false)
-
assert_done
-
-
args.each do |k, v|
-
k.options = @options
-
v.options = @options if v
-
end
-
args
-
rescue Sass::SyntaxError => e
-
e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
-
raise e
-
end
-
-
# Parses the argument list for a function definition.
-
#
-
# @return [Array<Script::Node>] The root nodes of the arguments.
-
# @raise [Sass::SyntaxError] if the argument list isn't valid SassScript
-
1
def parse_function_definition_arglist
-
args = defn_arglist!(true)
-
assert_done
-
-
args.each do |k, v|
-
k.options = @options
-
v.options = @options if v
-
end
-
args
-
rescue Sass::SyntaxError => e
-
e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
-
raise e
-
end
-
-
# Parses a SassScript expression.
-
#
-
# @overload parse(str, line, offset, filename = nil)
-
# @return [Script::Node] The root node of the parse tree
-
# @see Parser#initialize
-
# @see Parser#parse
-
1
def self.parse(*args)
-
new(*args).parse
-
end
-
-
1
PRECEDENCE = [
-
:comma, :single_eq, :space, :or, :and,
-
[:eq, :neq],
-
[:gt, :gte, :lt, :lte],
-
[:plus, :minus],
-
[:times, :div, :mod],
-
]
-
-
1
ASSOCIATIVE = [:plus, :times]
-
-
1
class << self
-
# Returns an integer representing the precedence
-
# of the given operator.
-
# A lower integer indicates a looser binding.
-
#
-
# @private
-
1
def precedence_of(op)
-
PRECEDENCE.each_with_index do |e, i|
-
return i if Array(e).include?(op)
-
end
-
raise "[BUG] Unknown operator #{op}"
-
end
-
-
# Returns whether or not the given operation is associative.
-
#
-
# @private
-
1
def associative?(op)
-
ASSOCIATIVE.include?(op)
-
end
-
-
1
private
-
-
# Defines a simple left-associative production.
-
# name is the name of the production,
-
# sub is the name of the production beneath it,
-
# and ops is a list of operators for this precedence level
-
1
def production(name, sub, *ops)
-
class_eval <<RUBY
-
def #{name}
-
interp = try_ops_after_interp(#{ops.inspect}, #{name.inspect}) and return interp
-
return unless e = #{sub}
-
15
while tok = try_tok(#{ops.map {|o| o.inspect}.join(', ')})
-
if interp = try_op_before_interp(tok, e)
-
return interp unless other_interp = try_ops_after_interp(#{ops.inspect}, #{name.inspect}, interp)
-
return other_interp
-
end
-
-
line = @lexer.line
-
e = Operation.new(e, assert_expr(#{sub.inspect}), tok.type)
-
e.line = line
-
end
-
e
-
end
-
8
RUBY
-
end
-
-
1
def unary(op, sub)
-
class_eval <<RUBY
-
def unary_#{op}
-
return #{sub} unless tok = try_tok(:#{op})
-
interp = try_op_before_interp(tok) and return interp
-
line = @lexer.line
-
op = UnaryOperation.new(assert_expr(:unary_#{op}), :#{op})
-
op.line = line
-
op
-
end
-
4
RUBY
-
end
-
end
-
-
1
private
-
-
# @private
-
1
def lexer_class; Lexer; end
-
-
1
def expr
-
interp = try_ops_after_interp([:comma], :expr) and return interp
-
line = @lexer.line
-
return unless e = interpolation
-
arr = [e]
-
while tok = try_tok(:comma)
-
if interp = try_op_before_interp(tok, e)
-
return interp unless other_interp = try_ops_after_interp([:comma], :expr, interp)
-
return other_interp
-
end
-
arr << assert_expr(:interpolation)
-
end
-
arr.size == 1 ? arr.first : node(List.new(arr, :comma), line)
-
end
-
-
1
production :equals, :interpolation, :single_eq
-
-
1
def try_op_before_interp(op, prev = nil)
-
return unless @lexer.peek && @lexer.peek.type == :begin_interpolation
-
wb = @lexer.whitespace?(op)
-
str = Script::String.new(Lexer::OPERATORS_REVERSE[op.type])
-
str.line = @lexer.line
-
interp = Script::Interpolation.new(prev, str, nil, wb, !:wa, :originally_text)
-
interp.line = @lexer.line
-
interpolation(interp)
-
end
-
-
1
def try_ops_after_interp(ops, name, prev = nil)
-
return unless @lexer.after_interpolation?
-
return unless op = try_tok(*ops)
-
interp = try_op_before_interp(op, prev) and return interp
-
-
wa = @lexer.whitespace?
-
str = Script::String.new(Lexer::OPERATORS_REVERSE[op.type])
-
str.line = @lexer.line
-
interp = Script::Interpolation.new(prev, str, assert_expr(name), !:wb, wa, :originally_text)
-
interp.line = @lexer.line
-
return interp
-
end
-
-
1
def interpolation(first = space)
-
e = first
-
while interp = try_tok(:begin_interpolation)
-
wb = @lexer.whitespace?(interp)
-
line = @lexer.line
-
mid = parse_interpolated
-
wa = @lexer.whitespace?
-
e = Script::Interpolation.new(e, mid, space, wb, wa)
-
e.line = line
-
end
-
e
-
end
-
-
1
def space
-
line = @lexer.line
-
return unless e = or_expr
-
arr = [e]
-
while e = or_expr
-
arr << e
-
end
-
arr.size == 1 ? arr.first : node(List.new(arr, :space), line)
-
end
-
-
1
production :or_expr, :and_expr, :or
-
1
production :and_expr, :eq_or_neq, :and
-
1
production :eq_or_neq, :relational, :eq, :neq
-
1
production :relational, :plus_or_minus, :gt, :gte, :lt, :lte
-
1
production :plus_or_minus, :times_div_or_mod, :plus, :minus
-
1
production :times_div_or_mod, :unary_plus, :times, :div, :mod
-
-
1
unary :plus, :unary_minus
-
1
unary :minus, :unary_div
-
1
unary :div, :unary_not # For strings, so /foo/bar works
-
1
unary :not, :ident
-
-
1
def ident
-
return funcall unless @lexer.peek && @lexer.peek.type == :ident
-
return if @stop_at && @stop_at.include?(@lexer.peek.value)
-
-
name = @lexer.next
-
if color = Color::HTML4_COLORS[name.value.downcase]
-
return node(Color.new(color))
-
end
-
node(Script::String.new(name.value, :identifier))
-
end
-
-
1
def funcall
-
return raw unless tok = try_tok(:funcall)
-
args, keywords = fn_arglist || [[], {}]
-
assert_tok(:rparen)
-
node(Script::Funcall.new(tok.value, args, keywords))
-
end
-
-
1
def defn_arglist!(must_have_parens)
-
if must_have_parens
-
assert_tok(:lparen)
-
else
-
return [] unless try_tok(:lparen)
-
end
-
return [] if try_tok(:rparen)
-
-
res = []
-
must_have_default = false
-
loop do
-
line = @lexer.line
-
offset = @lexer.offset + 1
-
c = assert_tok(:const)
-
var = Script::Variable.new(c.value)
-
if tok = try_tok(:colon)
-
val = assert_expr(:space)
-
must_have_default = true
-
elsif must_have_default
-
raise SyntaxError.new("Required argument #{var.inspect} must come before any optional arguments.")
-
end
-
res << [var, val]
-
break unless try_tok(:comma)
-
end
-
assert_tok(:rparen)
-
res
-
end
-
-
1
def fn_arglist
-
arglist(:fn_arglist, :equals)
-
end
-
-
1
def mixin_arglist
-
arglist(:mixin_arglist, :interpolation)
-
end
-
-
1
def arglist(type, subexpr)
-
return unless e = send(subexpr)
-
if @lexer.peek && @lexer.peek.type == :colon
-
name = e
-
@lexer.expected!("comma") unless name.is_a?(Variable)
-
assert_tok(:colon)
-
keywords = {name.underscored_name => assert_expr(subexpr, EXPR_NAMES[type])}
-
end
-
-
unless try_tok(:comma)
-
return [], keywords if keywords
-
return [e], {}
-
end
-
-
other_args, other_keywords = assert_expr(type)
-
if keywords
-
if other_keywords[name.underscored_name]
-
raise SyntaxError.new("Keyword argument \"#{name.to_sass}\" passed more than once")
-
end
-
return other_args, keywords.merge(other_keywords)
-
else
-
return [e, *other_args], other_keywords
-
end
-
end
-
-
1
def keyword_arglist
-
return unless var = try_tok(:const)
-
unless try_tok(:colon)
-
return_tok!
-
return
-
end
-
name = var[1]
-
value = interpolation
-
return {name => value} unless try_tok(:comma)
-
{name => value}.merge(assert_expr(:keyword_arglist))
-
end
-
-
1
def raw
-
return special_fun unless tok = try_tok(:raw)
-
node(Script::String.new(tok.value))
-
end
-
-
1
def special_fun
-
return paren unless tok = try_tok(:special_fun)
-
first = node(Script::String.new(tok.value.first))
-
Sass::Util.enum_slice(tok.value[1..-1], 2).inject(first) do |l, (i, r)|
-
Script::Interpolation.new(
-
l, i, r && node(Script::String.new(r)),
-
false, false)
-
end
-
end
-
-
1
def paren
-
return variable unless try_tok(:lparen)
-
was_in_parens = @in_parens
-
@in_parens = true
-
line = @lexer.line
-
e = expr
-
assert_tok(:rparen)
-
return e || node(List.new([], :space), line)
-
ensure
-
@in_parens = was_in_parens
-
end
-
-
1
def variable
-
return string unless c = try_tok(:const)
-
node(Variable.new(*c.value))
-
end
-
-
1
def string
-
return number unless first = try_tok(:string)
-
return first.value unless try_tok(:begin_interpolation)
-
line = @lexer.line
-
mid = parse_interpolated
-
last = assert_expr(:string)
-
interp = StringInterpolation.new(first.value, mid, last)
-
interp.line = line
-
interp
-
end
-
-
1
def number
-
return literal unless tok = try_tok(:number)
-
num = tok.value
-
num.original = num.to_s unless @in_parens
-
num
-
end
-
-
1
def literal
-
(t = try_tok(:color, :bool)) && (return t.value)
-
end
-
-
# It would be possible to have unified #assert and #try methods,
-
# but detecting the method/token difference turns out to be quite expensive.
-
-
1
EXPR_NAMES = {
-
:string => "string",
-
:default => "expression (e.g. 1px, bold)",
-
:mixin_arglist => "mixin argument",
-
:fn_arglist => "function argument",
-
}
-
-
1
def assert_expr(name, expected = nil)
-
(e = send(name)) && (return e)
-
@lexer.expected!(expected || EXPR_NAMES[name] || EXPR_NAMES[:default])
-
end
-
-
1
def assert_tok(*names)
-
(t = try_tok(*names)) && (return t)
-
@lexer.expected!(names.map {|tok| Lexer::TOKEN_NAMES[tok] || tok}.join(" or "))
-
end
-
-
1
def try_tok(*names)
-
peeked = @lexer.peek
-
peeked && names.include?(peeked.type) && @lexer.next
-
end
-
-
1
def assert_done
-
return if @lexer.done?
-
@lexer.expected!(EXPR_NAMES[:default])
-
end
-
-
1
def node(node, line = @lexer.line)
-
node.line = line
-
node
-
end
-
end
-
end
-
end
-
1
require 'sass/script/literal'
-
-
1
module Sass::Script
-
# A SassScript object representing a CSS string *or* a CSS identifier.
-
1
class String < Literal
-
# The Ruby value of the string.
-
#
-
# @return [String]
-
1
attr_reader :value
-
-
# Whether this is a CSS string or a CSS identifier.
-
# The difference is that strings are written with double-quotes,
-
# while identifiers aren't.
-
#
-
# @return [Symbol] `:string` or `:identifier`
-
1
attr_reader :type
-
-
# Creates a new string.
-
#
-
# @param value [String] See \{#value}
-
# @param type [Symbol] See \{#type}
-
1
def initialize(value, type = :identifier)
-
super(value)
-
@type = type
-
end
-
-
# @see Literal#plus
-
1
def plus(other)
-
other_str = other.is_a?(Sass::Script::String) ? other.value : other.to_s
-
Sass::Script::String.new(self.value + other_str, self.type)
-
end
-
-
# @see Node#to_s
-
1
def to_s(opts = {})
-
if @type == :identifier
-
return @value.tr("\n", " ")
-
end
-
-
return "\"#{value.gsub('"', "\\\"")}\"" if opts[:quote] == %q{"}
-
return "'#{value.gsub("'", "\\'")}'" if opts[:quote] == %q{'}
-
return "\"#{value}\"" unless value.include?('"')
-
return "'#{value}'" unless value.include?("'")
-
"\"#{value.gsub('"', "\\\"")}\"" #'
-
end
-
-
# @see Node#to_sass
-
1
def to_sass(opts = {})
-
to_s
-
end
-
end
-
end
-
1
module Sass::Script
-
# A SassScript object representing `#{}` interpolation within a string.
-
#
-
# @see Interpolation
-
1
class StringInterpolation < Node
-
# Interpolation in a string is of the form `"before #{mid} after"`,
-
# where `before` and `after` may include more interpolation.
-
#
-
# @param before [Node] The string before the interpolation
-
# @param mid [Node] The SassScript within the interpolation
-
# @param after [Node] The string after the interpolation
-
1
def initialize(before, mid, after)
-
@before = before
-
@mid = mid
-
@after = after
-
end
-
-
# @return [String] A human-readable s-expression representation of the interpolation
-
1
def inspect
-
"(string_interpolation #{@before.inspect} #{@mid.inspect} #{@after.inspect})"
-
end
-
-
# @see Node#to_sass
-
1
def to_sass(opts = {})
-
# We can get rid of all of this when we remove the deprecated :equals context
-
# XXX CE: It's gone now but I'm not sure what can be removed now.
-
before_unquote, before_quote_char, before_str = parse_str(@before.to_sass(opts))
-
after_unquote, after_quote_char, after_str = parse_str(@after.to_sass(opts))
-
unquote = before_unquote || after_unquote ||
-
(before_quote_char && !after_quote_char && !after_str.empty?) ||
-
(!before_quote_char && after_quote_char && !before_str.empty?)
-
quote_char =
-
if before_quote_char && after_quote_char && before_quote_char != after_quote_char
-
before_str.gsub!("\\'", "'")
-
before_str.gsub!('"', "\\\"")
-
after_str.gsub!("\\'", "'")
-
after_str.gsub!('"', "\\\"")
-
'"'
-
else
-
before_quote_char || after_quote_char
-
end
-
-
res = ""
-
res << 'unquote(' if unquote
-
res << quote_char if quote_char
-
res << before_str
-
res << '#{' << @mid.to_sass(opts) << '}'
-
res << after_str
-
res << quote_char if quote_char
-
res << ')' if unquote
-
res
-
end
-
-
# Returns the three components of the interpolation, `before`, `mid`, and `after`.
-
#
-
# @return [Array<Node>]
-
# @see #initialize
-
# @see Node#children
-
1
def children
-
[@before, @mid, @after].compact
-
end
-
-
# @see Node#deep_copy
-
1
def deep_copy
-
node = dup
-
node.instance_variable_set('@before', @before.deep_copy) if @before
-
node.instance_variable_set('@mid', @mid.deep_copy)
-
node.instance_variable_set('@after', @after.deep_copy) if @after
-
node
-
end
-
-
1
protected
-
-
# Evaluates the interpolation.
-
#
-
# @param environment [Sass::Environment] The environment in which to evaluate the SassScript
-
# @return [Sass::Script::String] The SassScript string that is the value of the interpolation
-
1
def _perform(environment)
-
res = ""
-
before = @before.perform(environment)
-
res << before.value
-
mid = @mid.perform(environment)
-
res << (mid.is_a?(Sass::Script::String) ? mid.value : mid.to_s)
-
res << @after.perform(environment).value
-
opts(Sass::Script::String.new(res, before.type))
-
end
-
-
1
private
-
-
1
def parse_str(str)
-
case str
-
when /^unquote\((["'])(.*)\1\)$/
-
return true, $1, $2
-
when '""'
-
return false, nil, ""
-
when /^(["'])(.*)\1$/
-
return false, $1, $2
-
else
-
return false, nil, str
-
end
-
end
-
end
-
end
-
1
module Sass::Script
-
# A SassScript parse node representing a unary operation,
-
# such as `-$b` or `not true`.
-
#
-
# Currently only `-`, `/`, and `not` are unary operators.
-
1
class UnaryOperation < Node
-
# @param operand [Script::Node] The parse-tree node
-
# for the object of the operator
-
# @param operator [Symbol] The operator to perform
-
1
def initialize(operand, operator)
-
@operand = operand
-
@operator = operator
-
super()
-
end
-
-
# @return [String] A human-readable s-expression representation of the operation
-
1
def inspect
-
"(#{@operator.inspect} #{@operand.inspect})"
-
end
-
-
# @see Node#to_sass
-
1
def to_sass(opts = {})
-
operand = @operand.to_sass(opts)
-
if @operand.is_a?(Operation) ||
-
(@operator == :minus &&
-
(operand =~ Sass::SCSS::RX::IDENT) == 0)
-
operand = "(#{@operand.to_sass(opts)})"
-
end
-
op = Lexer::OPERATORS_REVERSE[@operator]
-
op + (op =~ /[a-z]/ ? " " : "") + operand
-
end
-
-
# Returns the operand of the operation.
-
#
-
# @return [Array<Node>]
-
# @see Node#children
-
1
def children
-
[@operand]
-
end
-
-
# @see Node#deep_copy
-
1
def deep_copy
-
node = dup
-
node.instance_variable_set('@operand', @operand.deep_copy)
-
node
-
end
-
-
1
protected
-
-
# Evaluates the operation.
-
#
-
# @param environment [Sass::Environment] The environment in which to evaluate the SassScript
-
# @return [Literal] The SassScript object that is the value of the operation
-
# @raise [Sass::SyntaxError] if the operation is undefined for the operand
-
1
def _perform(environment)
-
operator = "unary_#{@operator}"
-
literal = @operand.perform(environment)
-
literal.send(operator)
-
rescue NoMethodError => e
-
raise e unless e.name.to_s == operator.to_s
-
raise Sass::SyntaxError.new("Undefined unary operation: \"#{@operator} #{literal}\".")
-
end
-
end
-
end
-
1
module Sass
-
1
module Script
-
# A SassScript parse node representing a variable.
-
1
class Variable < Node
-
# The name of the variable.
-
#
-
# @return [String]
-
1
attr_reader :name
-
-
# The underscored name of the variable.
-
#
-
# @return [String]
-
1
attr_reader :underscored_name
-
-
# @param name [String] See \{#name}
-
1
def initialize(name)
-
@name = name
-
@underscored_name = name.gsub(/-/,"_")
-
super()
-
end
-
-
# @return [String] A string representation of the variable
-
1
def inspect(opts = {})
-
"$#{dasherize(name, opts)}"
-
end
-
1
alias_method :to_sass, :inspect
-
-
# Returns an empty array.
-
#
-
# @return [Array<Node>] empty
-
# @see Node#children
-
1
def children
-
[]
-
end
-
-
# @see Node#deep_copy
-
1
def deep_copy
-
dup
-
end
-
-
1
protected
-
-
# Evaluates the variable.
-
#
-
# @param environment [Sass::Environment] The environment in which to evaluate the SassScript
-
# @return [Literal] The SassScript object that is the value of the variable
-
# @raise [Sass::SyntaxError] if the variable is undefined
-
1
def _perform(environment)
-
raise SyntaxError.new("Undefined variable: \"$#{name}\".") unless val = environment.var(name)
-
if val.is_a?(Number)
-
val = val.dup
-
val.original = nil
-
end
-
return val
-
end
-
end
-
end
-
end
-
1
require 'sass/scss/rx'
-
1
require 'sass/scss/script_lexer'
-
1
require 'sass/scss/script_parser'
-
1
require 'sass/scss/parser'
-
1
require 'sass/scss/sass_parser'
-
1
require 'sass/scss/static_parser'
-
1
require 'sass/scss/css_parser'
-
-
1
module Sass
-
# SCSS is the CSS syntax for Sass.
-
# It parses into the same syntax tree as Sass,
-
# and generates the same sort of output CSS.
-
#
-
# This module contains code for the parsing of SCSS.
-
# The evaluation is handled by the broader {Sass} module.
-
1
module SCSS; end
-
end
-
1
require 'sass/script/css_parser'
-
-
1
module Sass
-
1
module SCSS
-
# This is a subclass of {Parser} which only parses plain CSS.
-
# It doesn't support any Sass extensions, such as interpolation,
-
# parent references, nested selectors, and so forth.
-
# It does support all the same CSS hacks as the SCSS parser, though.
-
1
class CssParser < StaticParser
-
# Parse a selector, and return its value as a string.
-
#
-
# @return [String, nil] The parsed selector, or nil if no selector was parsed
-
# @raise [Sass::SyntaxError] if there's a syntax error in the selector
-
1
def parse_selector_string
-
init_scanner!
-
str {return unless selector}
-
end
-
-
1
private
-
-
1
def parent_selector; nil; end
-
1
def interpolation; nil; end
-
1
def interp_string; tok(STRING); end
-
1
def interp_ident(ident = IDENT); tok(ident); end
-
1
def use_css_import?; true; end
-
-
1
def block_child(context)
-
case context
-
when :ruleset
-
declaration
-
when :stylesheet
-
directive || ruleset
-
when :directive
-
directive || declaration_or_ruleset
-
end
-
end
-
-
1
def nested_properties!(node, space)
-
expected('expression (e.g. 1px, bold)');
-
end
-
-
1
@sass_script_parser = Class.new(Sass::Script::CssParser)
-
1
@sass_script_parser.send(:include, ScriptParser)
-
end
-
end
-
end
-
1
require 'set'
-
-
1
module Sass
-
1
module SCSS
-
# The parser for SCSS.
-
# It parses a string of code into a tree of {Sass::Tree::Node}s.
-
1
class Parser
-
# @param str [String, StringScanner] The source document to parse.
-
# Note that `Parser` *won't* raise a nice error message if this isn't properly parsed;
-
# for that, you should use the higher-level {Sass::Engine} or {Sass::CSS}.
-
# @param filename [String] The name of the file being parsed. Used for warnings.
-
# @param line [Fixnum] The line on which the source string appeared,
-
# if it's part of another document.
-
1
def initialize(str, filename, line = 1)
-
@template = str
-
@filename = filename
-
@line = line
-
@strs = []
-
end
-
-
# Parses an SCSS document.
-
#
-
# @return [Sass::Tree::RootNode] The root node of the document tree
-
# @raise [Sass::SyntaxError] if there's a syntax error in the document
-
1
def parse
-
init_scanner!
-
root = stylesheet
-
expected("selector or at-rule") unless @scanner.eos?
-
root
-
end
-
-
# Parses an identifier with interpolation.
-
# Note that this won't assert that the identifier takes up the entire input string;
-
# it's meant to be used with `StringScanner`s as part of other parsers.
-
#
-
# @return [Array<String, Sass::Script::Node>, nil]
-
# The interpolated identifier, or nil if none could be parsed
-
1
def parse_interp_ident
-
init_scanner!
-
interp_ident
-
end
-
-
1
private
-
-
1
include Sass::SCSS::RX
-
-
1
def init_scanner!
-
@scanner =
-
if @template.is_a?(StringScanner)
-
@template
-
else
-
Sass::Util::MultibyteStringScanner.new(@template.gsub("\r", ""))
-
end
-
end
-
-
1
def stylesheet
-
node = node(Sass::Tree::RootNode.new(@scanner.string))
-
block_contents(node, :stylesheet) {s(node)}
-
end
-
-
1
def s(node)
-
while tok(S) || tok(CDC) || tok(CDO) || (c = tok(SINGLE_LINE_COMMENT)) || (c = tok(COMMENT))
-
next unless c
-
process_comment c, node
-
c = nil
-
end
-
true
-
end
-
-
1
def ss
-
nil while tok(S) || tok(SINGLE_LINE_COMMENT) || tok(COMMENT)
-
true
-
end
-
-
1
def ss_comments(node)
-
while tok(S) || (c = tok(SINGLE_LINE_COMMENT)) || (c = tok(COMMENT))
-
next unless c
-
process_comment c, node
-
c = nil
-
end
-
-
true
-
end
-
-
1
def whitespace
-
return unless tok(S) || tok(SINGLE_LINE_COMMENT) || tok(COMMENT)
-
ss
-
end
-
-
1
def process_comment(text, node)
-
silent = text =~ /^\/\//
-
line = @line - text.count("\n")
-
if loud = text =~ %r{^/[/*]!}
-
value = Sass::Engine.parse_interp(text, line, @scanner.pos - text.size, :filename => @filename)
-
value[0].slice!(2) # get rid of the "!"
-
else
-
value = [text]
-
end
-
-
if silent
-
value = Sass::Util.with_extracted_values(value) do |str|
-
str.sub(/^\s*\/\//, '/*').gsub(/^\s*\/\//, ' *') + ' */'
-
end
-
else
-
value.unshift(@scanner.
-
string[0...@scanner.pos].
-
reverse[/.*?\*\/(.*?)($|\Z)/, 1].
-
reverse.gsub(/[^\s]/, ' '))
-
end
-
-
comment = Sass::Tree::CommentNode.new(value, silent, loud)
-
comment.line = line
-
node << comment
-
end
-
-
1
DIRECTIVES = Set[:mixin, :include, :function, :return, :debug, :warn, :for,
-
:each, :while, :if, :else, :extend, :import, :media, :charset, :_moz_document]
-
-
1
PREFIXED_DIRECTIVES = Set[:supports]
-
-
1
def directive
-
return unless tok(/@/)
-
name = tok!(IDENT)
-
ss
-
-
if dir = special_directive(name)
-
return dir
-
elsif dir = prefixed_directive(name)
-
return dir
-
end
-
-
# Most at-rules take expressions (e.g. @import),
-
# but some (e.g. @page) take selector-like arguments
-
val = str {break unless expr}
-
val ||= CssParser.new(@scanner, @line).parse_selector_string
-
directive_body("@#{name} #{val}")
-
end
-
-
1
def directive_body(value)
-
node = node(Sass::Tree::DirectiveNode.new(value.strip))
-
-
if tok(/\{/)
-
node.has_children = true
-
block_contents(node, :directive)
-
tok!(/\}/)
-
end
-
-
node
-
end
-
-
1
def special_directive(name)
-
sym = name.gsub('-', '_').to_sym
-
DIRECTIVES.include?(sym) && send("#{sym}_directive")
-
end
-
-
1
def prefixed_directive(name)
-
sym = name.gsub(/^-[a-z0-9]+-/i, '').gsub('-', '_').to_sym
-
PREFIXED_DIRECTIVES.include?(sym) && send("#{sym}_directive", name)
-
end
-
-
1
def mixin_directive
-
name = tok! IDENT
-
args = sass_script(:parse_mixin_definition_arglist)
-
ss
-
block(node(Sass::Tree::MixinDefNode.new(name, args)), :directive)
-
end
-
-
1
def include_directive
-
name = tok! IDENT
-
args, keywords = sass_script(:parse_mixin_include_arglist)
-
ss
-
node(Sass::Tree::MixinNode.new(name, args, keywords))
-
end
-
-
1
def function_directive
-
name = tok! IDENT
-
args = sass_script(:parse_function_definition_arglist)
-
ss
-
block(node(Sass::Tree::FunctionNode.new(name, args)), :function)
-
end
-
-
1
def return_directive
-
node(Sass::Tree::ReturnNode.new(sass_script(:parse)))
-
end
-
-
1
def debug_directive
-
node(Sass::Tree::DebugNode.new(sass_script(:parse)))
-
end
-
-
1
def warn_directive
-
node(Sass::Tree::WarnNode.new(sass_script(:parse)))
-
end
-
-
1
def for_directive
-
tok!(/\$/)
-
var = tok! IDENT
-
ss
-
-
tok!(/from/)
-
from = sass_script(:parse_until, Set["to", "through"])
-
ss
-
-
@expected = '"to" or "through"'
-
exclusive = (tok(/to/) || tok!(/through/)) == 'to'
-
to = sass_script(:parse)
-
ss
-
-
block(node(Sass::Tree::ForNode.new(var, from, to, exclusive)), :directive)
-
end
-
-
1
def each_directive
-
tok!(/\$/)
-
var = tok! IDENT
-
ss
-
-
tok!(/in/)
-
list = sass_script(:parse)
-
ss
-
-
block(node(Sass::Tree::EachNode.new(var, list)), :directive)
-
end
-
-
1
def while_directive
-
expr = sass_script(:parse)
-
ss
-
block(node(Sass::Tree::WhileNode.new(expr)), :directive)
-
end
-
-
1
def if_directive
-
expr = sass_script(:parse)
-
ss
-
node = block(node(Sass::Tree::IfNode.new(expr)), :directive)
-
pos = @scanner.pos
-
line = @line
-
ss
-
-
else_block(node) ||
-
begin
-
# Backtrack in case there are any comments we want to parse
-
@scanner.pos = pos
-
@line = line
-
node
-
end
-
end
-
-
1
def else_block(node)
-
return unless tok(/@else/)
-
ss
-
else_node = block(
-
Sass::Tree::IfNode.new((sass_script(:parse) if tok(/if/))),
-
:directive)
-
node.add_else(else_node)
-
pos = @scanner.pos
-
line = @line
-
ss
-
-
else_block(node) ||
-
begin
-
# Backtrack in case there are any comments we want to parse
-
@scanner.pos = pos
-
@line = line
-
node
-
end
-
end
-
-
1
def else_directive
-
err("Invalid CSS: @else must come after @if")
-
end
-
-
1
def extend_directive
-
node(Sass::Tree::ExtendNode.new(expr!(:selector_sequence)))
-
end
-
-
1
def import_directive
-
values = []
-
-
loop do
-
values << expr!(:import_arg)
-
break if use_css_import? || !tok(/,\s*/)
-
end
-
-
return values
-
end
-
-
1
def import_arg
-
return unless arg = tok(STRING) || (uri = tok!(URI))
-
path = @scanner[1] || @scanner[2] || @scanner[3]
-
ss
-
-
media = str {media_query_list}.strip
-
-
if uri || path =~ /^http:\/\// || !media.strip.empty? || use_css_import?
-
return node(Sass::Tree::DirectiveNode.new("@import #{arg} #{media}".strip))
-
end
-
-
node(Sass::Tree::ImportNode.new(path.strip))
-
end
-
-
1
def use_css_import?; false; end
-
-
1
def media_directive
-
block(node(Sass::Tree::MediaNode.new(media_query_list)), :directive)
-
end
-
-
# http://www.w3.org/TR/css3-mediaqueries/#syntax
-
1
def media_query_list
-
has_q = false
-
q = str {has_q = media_query}
-
-
return unless has_q
-
queries = [q.strip]
-
-
ss
-
while tok(/,/)
-
ss; queries << str {expr!(:media_query)}.strip; ss
-
end
-
-
queries
-
end
-
-
1
def media_query
-
if tok(/only|not/i)
-
ss
-
@expected = "media type (e.g. print, screen)"
-
tok!(IDENT)
-
ss
-
elsif !tok(IDENT) && !media_expr
-
return
-
end
-
-
ss
-
while tok(/and/i)
-
ss; expr!(:media_expr); ss
-
end
-
-
true
-
end
-
-
1
def media_expr
-
return unless tok(/\(/)
-
ss
-
@expected = "media feature (e.g. min-device-width, color)"
-
tok!(IDENT)
-
ss
-
-
if tok(/:/)
-
ss; expr!(:expr)
-
end
-
tok!(/\)/)
-
ss
-
-
true
-
end
-
-
1
def charset_directive
-
tok! STRING
-
name = @scanner[1] || @scanner[2]
-
ss
-
node(Sass::Tree::CharsetNode.new(name))
-
end
-
-
# The document directive is specified in
-
# http://www.w3.org/TR/css3-conditional/, but Gecko allows the
-
# `url-prefix` and `domain` functions to omit quotation marks, contrary to
-
# the standard.
-
#
-
# We could parse all document directives according to Mozilla's syntax,
-
# but if someone's using e.g. @-webkit-document we don't want them to
-
# think WebKit works sans quotes.
-
1
def _moz_document_directive
-
value = str do
-
begin
-
ss
-
expr!(:moz_document_function)
-
end while tok(/,/)
-
end
-
directive_body("@-moz-document #{value}")
-
end
-
-
1
def moz_document_function
-
return unless tok(URI) || tok(URL_PREFIX) || tok(DOMAIN) || function
-
ss
-
end
-
-
# http://www.w3.org/TR/css3-conditional/
-
1
def supports_directive(name)
-
value = str {expr!(:supports_condition)}
-
directive_body("@#{name} #{value}")
-
end
-
-
1
def supports_condition
-
supports_negation || supports_operator || supports_declaration_condition
-
end
-
-
1
def supports_negation
-
return unless tok(/not/i)
-
ss
-
expr!(:supports_condition_in_parens)
-
end
-
-
1
def supports_operator
-
return unless supports_condition_in_parens
-
tok!(/and|or/i)
-
begin
-
ss
-
expr!(:supports_condition_in_parens)
-
end while tok(/and|or/i)
-
true
-
end
-
-
1
def supports_condition_in_parens
-
return unless tok(/\(/); ss
-
if supports_condition
-
tok!(/\)/); ss
-
else
-
supports_declaration_body
-
end
-
end
-
-
1
def supports_declaration_condition
-
return unless tok(/\(/); ss
-
supports_declaration_body
-
end
-
-
1
def supports_declaration_body
-
tok!(IDENT); ss
-
tok!(/:/); ss
-
expr!(:expr); ss
-
tok!(/\)/); ss
-
end
-
-
1
def variable
-
return unless tok(/\$/)
-
name = tok!(IDENT)
-
ss; tok!(/:/); ss
-
-
expr = sass_script(:parse)
-
guarded = tok(DEFAULT)
-
node(Sass::Tree::VariableNode.new(name, expr, guarded))
-
end
-
-
1
def operator
-
# Many of these operators (all except / and ,)
-
# are disallowed by the CSS spec,
-
# but they're included here for compatibility
-
# with some proprietary MS properties
-
str {ss if tok(/[\/,:.=]/)}
-
end
-
-
1
def unary_operator
-
tok(/[+-]/)
-
end
-
-
1
def ruleset
-
return unless rules = selector_sequence
-
block(node(Sass::Tree::RuleNode.new(rules.flatten.compact)), :ruleset)
-
end
-
-
1
def block(node, context)
-
node.has_children = true
-
tok!(/\{/)
-
block_contents(node, context)
-
tok!(/\}/)
-
node
-
end
-
-
# A block may contain declarations and/or rulesets
-
1
def block_contents(node, context)
-
block_given? ? yield : ss_comments(node)
-
node << (child = block_child(context))
-
while tok(/;/) || has_children?(child)
-
block_given? ? yield : ss_comments(node)
-
node << (child = block_child(context))
-
end
-
node
-
end
-
-
1
def block_child(context)
-
return variable || directive if context == :function
-
return variable || directive || ruleset if context == :stylesheet
-
variable || directive || declaration_or_ruleset
-
end
-
-
1
def has_children?(child_or_array)
-
return false unless child_or_array
-
return child_or_array.last.has_children if child_or_array.is_a?(Array)
-
return child_or_array.has_children
-
end
-
-
# This is a nasty hack, and the only place in the parser
-
# that requires backtracking.
-
# The reason is that we can't figure out if certain strings
-
# are declarations or rulesets with fixed finite lookahead.
-
# For example, "foo:bar baz baz baz..." could be either a property
-
# or a selector.
-
#
-
# To handle this, we simply check if it works as a property
-
# (which is the most common case)
-
# and, if it doesn't, try it as a ruleset.
-
#
-
# We could eke some more efficiency out of this
-
# by handling some easy cases (first token isn't an identifier,
-
# no colon after the identifier, whitespace after the colon),
-
# but I'm not sure the gains would be worth the added complexity.
-
1
def declaration_or_ruleset
-
old_use_property_exception, @use_property_exception =
-
@use_property_exception, false
-
decl_err = catch_error do
-
decl = declaration
-
unless decl && decl.has_children
-
# We want an exception if it's not there,
-
# but we don't want to consume if it is
-
tok!(/[;}]/) unless tok?(/[;}]/)
-
end
-
return decl
-
end
-
-
ruleset_err = catch_error {return ruleset}
-
rethrow(@use_property_exception ? decl_err : ruleset_err)
-
ensure
-
@use_property_exception = old_use_property_exception
-
end
-
-
1
def selector_sequence
-
if sel = tok(STATIC_SELECTOR, true)
-
return [sel]
-
end
-
-
rules = []
-
return unless v = selector
-
rules.concat v
-
-
ws = ''
-
while tok(/,/)
-
ws << str {ss}
-
if v = selector
-
rules << ',' << ws
-
rules.concat v
-
ws = ''
-
end
-
end
-
rules
-
end
-
-
1
def selector
-
return unless sel = _selector
-
sel.to_a
-
end
-
-
1
def selector_comma_sequence
-
return unless sel = _selector
-
selectors = [sel]
-
ws = ''
-
while tok(/,/)
-
ws << str{ss}
-
if sel = _selector
-
selectors << sel
-
selectors[-1] = Selector::Sequence.new(["\n"] + selectors.last.members) if ws.include?("\n")
-
ws = ''
-
end
-
end
-
Selector::CommaSequence.new(selectors)
-
end
-
-
1
def _selector
-
# The combinator here allows the "> E" hack
-
return unless val = combinator || simple_selector_sequence
-
nl = str{ss}.include?("\n")
-
res = []
-
res << val
-
res << "\n" if nl
-
-
while val = combinator || simple_selector_sequence
-
res << val
-
res << "\n" if str{ss}.include?("\n")
-
end
-
Selector::Sequence.new(res.compact)
-
end
-
-
1
def combinator
-
tok(PLUS) || tok(GREATER) || tok(TILDE)
-
end
-
-
1
def simple_selector_sequence
-
# This allows for stuff like http://www.w3.org/TR/css3-animations/#keyframes-
-
return expr unless e = element_name || id_selector || class_selector ||
-
attrib || negation || pseudo || parent_selector || interpolation_selector
-
res = [e]
-
-
# The tok(/\*/) allows the "E*" hack
-
while v = id_selector || class_selector || attrib || negation || pseudo ||
-
interpolation_selector || (tok(/\*/) && Selector::Universal.new(nil))
-
res << v
-
end
-
-
pos = @scanner.pos
-
line = @line
-
if sel = str? {simple_selector_sequence}
-
@scanner.pos = pos
-
@line = line
-
-
if sel =~ /^&/
-
begin
-
throw_error {expected('"{"')}
-
rescue Sass::SyntaxError => e
-
e.message << "\n\n\"#{sel}\" may only be used at the beginning of a selector."
-
raise e
-
end
-
else
-
Sass::Util.sass_warn(<<MESSAGE)
-
DEPRECATION WARNING:
-
On line #{@line}#{" of \"#{@filename}\"" if @filename}, after "#{self.class.prior_snippet(@scanner)}"
-
Starting in Sass 3.2, "#{sel}" may only be used at the beginning of a selector.
-
MESSAGE
-
end
-
end
-
-
Selector::SimpleSequence.new(res)
-
end
-
-
1
def parent_selector
-
return unless tok(/&/)
-
Selector::Parent.new
-
end
-
-
1
def class_selector
-
return unless tok(/\./)
-
@expected = "class name"
-
Selector::Class.new(merge(expr!(:interp_ident)))
-
end
-
-
1
def id_selector
-
return unless tok(/#(?!\{)/)
-
@expected = "id name"
-
Selector::Id.new(merge(expr!(:interp_name)))
-
end
-
-
1
def element_name
-
return unless name = interp_ident || tok(/\*/) || (tok?(/\|/) && "")
-
if tok(/\|/)
-
@expected = "element name or *"
-
ns = name
-
name = interp_ident || tok!(/\*/)
-
end
-
-
if name == '*'
-
Selector::Universal.new(merge(ns))
-
else
-
Selector::Element.new(merge(name), merge(ns))
-
end
-
end
-
-
1
def interpolation_selector
-
return unless script = interpolation
-
Selector::Interpolation.new(script)
-
end
-
-
1
def attrib
-
return unless tok(/\[/)
-
ss
-
ns, name = attrib_name!
-
ss
-
-
if op = tok(/=/) ||
-
tok(INCLUDES) ||
-
tok(DASHMATCH) ||
-
tok(PREFIXMATCH) ||
-
tok(SUFFIXMATCH) ||
-
tok(SUBSTRINGMATCH)
-
@expected = "identifier or string"
-
ss
-
if val = tok(IDENT)
-
val = [val]
-
else
-
val = expr!(:interp_string)
-
end
-
ss
-
end
-
tok(/\]/)
-
-
Selector::Attribute.new(merge(name), merge(ns), op, merge(val))
-
end
-
-
1
def attrib_name!
-
if name_or_ns = interp_ident
-
# E, E|E
-
if tok(/\|(?!=)/)
-
ns = name_or_ns
-
name = interp_ident
-
else
-
name = name_or_ns
-
end
-
else
-
# *|E or |E
-
ns = [tok(/\*/) || ""]
-
tok!(/\|/)
-
name = expr!(:interp_ident)
-
end
-
return ns, name
-
end
-
-
1
def pseudo
-
return unless s = tok(/::?/)
-
@expected = "pseudoclass or pseudoelement"
-
name = expr!(:interp_ident)
-
if tok(/\(/)
-
ss
-
arg = expr!(:pseudo_expr)
-
tok!(/\)/)
-
end
-
Selector::Pseudo.new(s == ':' ? :class : :element, merge(name), merge(arg))
-
end
-
-
1
def pseudo_expr
-
return unless e = tok(PLUS) || tok(/-/) || tok(NUMBER) ||
-
interp_string || tok(IDENT) || interpolation
-
res = [e, str{ss}]
-
while e = tok(PLUS) || tok(/-/) || tok(NUMBER) ||
-
interp_string || tok(IDENT) || interpolation
-
res << e << str{ss}
-
end
-
res
-
end
-
-
1
def negation
-
return unless name = tok(NOT) || tok(MOZ_ANY)
-
ss
-
@expected = "selector"
-
sel = selector_comma_sequence
-
tok!(/\)/)
-
Selector::SelectorPseudoClass.new(name[1...-1], sel)
-
end
-
-
1
def declaration
-
# This allows the "*prop: val", ":prop: val", and ".prop: val" hacks
-
if s = tok(/[:\*\.]|\#(?!\{)/)
-
@use_property_exception = s !~ /[\.\#]/
-
name = [s, str{ss}, *expr!(:interp_ident)]
-
else
-
return unless name = interp_ident
-
name = [name] if name.is_a?(String)
-
end
-
if comment = tok(COMMENT)
-
name << comment
-
end
-
ss
-
-
tok!(/:/)
-
space, value = value!
-
ss
-
require_block = tok?(/\{/)
-
-
node = node(Sass::Tree::PropNode.new(name.flatten.compact, value, :new))
-
-
return node unless require_block
-
nested_properties! node, space
-
end
-
-
1
def value!
-
space = !str {ss}.empty?
-
@use_property_exception ||= space || !tok?(IDENT)
-
-
return true, Sass::Script::String.new("") if tok?(/\{/)
-
# This is a bit of a dirty trick:
-
# if the value is completely static,
-
# we don't parse it at all, and instead return a plain old string
-
# containing the value.
-
# This results in a dramatic speed increase.
-
if val = tok(STATIC_VALUE, true)
-
return space, Sass::Script::String.new(val.strip)
-
end
-
return space, sass_script(:parse)
-
end
-
-
1
def plain_value
-
return unless tok(/:/)
-
space = !str {ss}.empty?
-
@use_property_exception ||= space || !tok?(IDENT)
-
-
expression = expr
-
expression << tok(IMPORTANT) if expression
-
# expression, space, value
-
return expression, space, expression || [""]
-
end
-
-
1
def nested_properties!(node, space)
-
err(<<MESSAGE) unless space
-
Invalid CSS: a space is required between a property and its definition
-
when it has other properties nested beneath it.
-
MESSAGE
-
-
@use_property_exception = true
-
@expected = 'expression (e.g. 1px, bold) or "{"'
-
block(node, :property)
-
end
-
-
1
def expr
-
return unless t = term
-
res = [t, str{ss}]
-
-
while (o = operator) && (t = term)
-
res << o << t << str{ss}
-
end
-
-
res
-
end
-
-
1
def term
-
unless e = tok(NUMBER) ||
-
tok(URI) ||
-
function ||
-
tok(STRING) ||
-
tok(UNICODERANGE) ||
-
tok(IDENT) ||
-
tok(HEXCOLOR)
-
-
return unless op = unary_operator
-
@expected = "number or function"
-
return [op, tok(NUMBER) || expr!(:function)]
-
end
-
e
-
end
-
-
1
def function
-
return unless name = tok(FUNCTION)
-
if name == "expression(" || name == "calc("
-
str, _ = Sass::Shared.balance(@scanner, ?(, ?), 1)
-
[name, str]
-
else
-
[name, str{ss}, expr, tok!(/\)/)]
-
end
-
end
-
-
1
def interpolation
-
return unless tok(INTERP_START)
-
sass_script(:parse_interpolated)
-
end
-
-
1
def interp_string
-
_interp_string(:double) || _interp_string(:single)
-
end
-
-
1
def _interp_string(type)
-
return unless start = tok(Sass::Script::Lexer::STRING_REGULAR_EXPRESSIONS[[type, false]])
-
res = [start]
-
-
mid_re = Sass::Script::Lexer::STRING_REGULAR_EXPRESSIONS[[type, true]]
-
# @scanner[2].empty? means we've started an interpolated section
-
while @scanner[2] == '#{'
-
@scanner.pos -= 2 # Don't consume the #{
-
res.last.slice!(-2..-1)
-
res << expr!(:interpolation) << tok(mid_re)
-
end
-
res
-
end
-
-
1
def interp_ident(start = IDENT)
-
return unless val = tok(start) || interpolation || tok(IDENT_HYPHEN_INTERP, true)
-
res = [val]
-
while val = tok(NAME) || interpolation
-
res << val
-
end
-
res
-
end
-
-
1
def interp_name
-
interp_ident NAME
-
end
-
-
1
def str
-
@strs.push ""
-
yield
-
@strs.last
-
ensure
-
@strs.pop
-
end
-
-
1
def str?
-
pos = @scanner.pos
-
line = @line
-
@strs.push ""
-
throw_error {yield} && @strs.last
-
rescue Sass::SyntaxError => e
-
@scanner.pos = pos
-
@line = line
-
nil
-
ensure
-
@strs.pop
-
end
-
-
1
def node(node)
-
node.line = @line
-
node
-
end
-
-
1
@sass_script_parser = Class.new(Sass::Script::Parser)
-
1
@sass_script_parser.send(:include, ScriptParser)
-
# @private
-
1
def self.sass_script_parser; @sass_script_parser; end
-
-
1
def sass_script(*args)
-
parser = self.class.sass_script_parser.new(@scanner, @line,
-
@scanner.pos - (@scanner.string[0...@scanner.pos].rindex("\n") || 0))
-
result = parser.send(*args)
-
@line = parser.line
-
result
-
rescue Sass::SyntaxError => e
-
throw(:_sass_parser_error, true) if @throw_error
-
raise e
-
end
-
-
1
def merge(arr)
-
arr && Sass::Util.merge_adjacent_strings([arr].flatten)
-
end
-
-
1
EXPR_NAMES = {
-
:media_query => "media query (e.g. print, screen, print and screen)",
-
:media_expr => "media expression (e.g. (min-device-width: 800px)))",
-
:pseudo_expr => "expression (e.g. fr, 2n+1)",
-
:interp_ident => "identifier",
-
:interp_name => "identifier",
-
:expr => "expression (e.g. 1px, bold)",
-
:_selector => "selector",
-
:selector_comma_sequence => "selector",
-
:simple_selector_sequence => "selector",
-
:import_arg => "file to import (string or url())",
-
:moz_document_function => "matching function (e.g. url-prefix(), domain())",
-
:supports_condition => "@supports condition (e.g. (display: flexbox))",
-
:supports_condition_in_parens => "@supports condition (e.g. (display: flexbox))",
-
}
-
-
1
TOK_NAMES = Sass::Util.to_hash(
-
50
Sass::SCSS::RX.constants.map {|c| [Sass::SCSS::RX.const_get(c), c.downcase]}).
-
merge(IDENT => "identifier", /[;}]/ => '";"')
-
-
1
def tok?(rx)
-
@scanner.match?(rx)
-
end
-
-
1
def expr!(name)
-
(e = send(name)) && (return e)
-
expected(EXPR_NAMES[name] || name.to_s)
-
end
-
-
1
def tok!(rx)
-
(t = tok(rx)) && (return t)
-
name = TOK_NAMES[rx]
-
-
unless name
-
# Display basic regexps as plain old strings
-
string = rx.source.gsub(/\\(.)/, '\1')
-
name = rx.source == Regexp.escape(string) ? string.inspect : rx.inspect
-
end
-
-
expected(name)
-
end
-
-
1
def expected(name)
-
throw(:_sass_parser_error, true) if @throw_error
-
self.class.expected(@scanner, @expected || name, @line)
-
end
-
-
1
def err(msg)
-
throw(:_sass_parser_error, true) if @throw_error
-
raise Sass::SyntaxError.new(msg, :line => @line)
-
end
-
-
1
def throw_error
-
old_throw_error, @throw_error = @throw_error, false
-
yield
-
ensure
-
@throw_error = old_throw_error
-
end
-
-
1
def catch_error(&block)
-
old_throw_error, @throw_error = @throw_error, true
-
pos = @scanner.pos
-
line = @line
-
expected = @expected
-
if catch(:_sass_parser_error, &block)
-
@scanner.pos = pos
-
@line = line
-
@expected = expected
-
{:pos => pos, :line => line, :expected => @expected, :block => block}
-
end
-
ensure
-
@throw_error = old_throw_error
-
end
-
-
1
def rethrow(err)
-
if @throw_err
-
throw :_sass_parser_error, err
-
else
-
@scanner = Sass::Util::MultibyteStringScanner.new(@scanner.string)
-
@scanner.pos = err[:pos]
-
@line = err[:line]
-
@expected = err[:expected]
-
err[:block].call
-
end
-
end
-
-
# @private
-
1
def self.expected(scanner, expected, line)
-
was = scanner.rest.dup
-
# Get rid of whitespace between pos and the next token,
-
# but only if there's a newline in there
-
was.gsub!(/^\s*\n\s*/, '')
-
# Also get rid of stuff after the next newline
-
was.gsub!(/\n.*/, '')
-
was = was[0...15] + "..." if was.size > 18
-
-
raise Sass::SyntaxError.new(
-
"Invalid CSS after \"#{prior_snippet(scanner)}\": expected #{expected}, was \"#{was}\"",
-
:line => line)
-
end
-
-
# @private
-
1
def self.prior_snippet(scanner)
-
pos = scanner.pos
-
-
after = scanner.string[0...pos]
-
# Get rid of whitespace between pos and the last token,
-
# but only if there's a newline in there
-
after.gsub!(/\s*\n\s*$/, '')
-
# Also get rid of stuff before the last newline
-
after.gsub!(/.*\n/, '')
-
after = "..." + after[-15..-1] if after.size > 18
-
after
-
end
-
-
# Avoid allocating lots of new strings for `#tok`.
-
# This is important because `#tok` is called all the time.
-
1
NEWLINE = "\n"
-
-
1
def tok(rx, last_group_lookahead = false)
-
res = @scanner.scan(rx)
-
if res
-
# This fixes https://github.com/nex3/sass/issues/104, which affects
-
# Ruby 1.8.7 and REE. This fix is to replace the ?= zero-width
-
# positive lookahead operator in the Regexp (which matches without
-
# consuming the matched group), with a match that does consume the
-
# group, but then rewinds the scanner and removes the group from the
-
# end of the matched string. This fix makes the assumption that the
-
# matched group will always occur at the end of the match.
-
if last_group_lookahead && @scanner[-1]
-
@scanner.pos -= @scanner[-1].length
-
res.slice!(-@scanner[-1].length..-1)
-
end
-
@line += res.count(NEWLINE)
-
@expected = nil
-
if !@strs.empty? && rx != COMMENT && rx != SINGLE_LINE_COMMENT
-
@strs.each {|s| s << res}
-
end
-
res
-
end
-
end
-
end
-
end
-
end
-
1
module Sass
-
1
module SCSS
-
# A module containing regular expressions used
-
# for lexing tokens in an SCSS document.
-
# Most of these are taken from [the CSS3 spec](http://www.w3.org/TR/css3-syntax/#lexical),
-
# although some have been modified for various reasons.
-
1
module RX
-
# Takes a string and returns a CSS identifier
-
# that will have the value of the given string.
-
#
-
# @param str [String] The string to escape
-
# @return [String] The escaped string
-
1
def self.escape_ident(str)
-
return "" if str.empty?
-
return "\\#{str}" if str == '-' || str == '_'
-
out = ""
-
value = str.dup
-
out << value.slice!(0...1) if value =~ /^[-_]/
-
if value[0...1] =~ NMSTART
-
out << value.slice!(0...1)
-
else
-
out << escape_char(value.slice!(0...1))
-
end
-
out << value.gsub(/[^a-zA-Z0-9_-]/) {|c| escape_char c}
-
return out
-
end
-
-
# Escapes a single character for a CSS identifier.
-
#
-
# @param c [String] The character to escape. Should have length 1
-
# @return [String] The escaped character
-
# @private
-
1
def self.escape_char(c)
-
return "\\%06x" % Sass::Util.ord(c) unless c =~ /[ -\/:-~]/
-
return "\\#{c}"
-
end
-
-
# Creates a Regexp from a plain text string,
-
# escaping all significant characters.
-
#
-
# @param str [String] The text of the regexp
-
# @param flags [Fixnum] Flags for the created regular expression
-
# @return [Regexp]
-
# @private
-
1
def self.quote(str, flags = 0)
-
9
Regexp.new(Regexp.quote(str), flags)
-
end
-
-
1
H = /[0-9a-fA-F]/
-
1
NL = /\n|\r\n|\r|\f/
-
1
UNICODE = /\\#{H}{1,6}[ \t\r\n\f]?/
-
1
s = if Sass::Util.ruby1_8?
-
'\200-\377'
-
else
-
1
'\u{80}-\u{D7FF}\u{E000}-\u{FFFD}\u{10000}-\u{10FFFF}'
-
end
-
1
NONASCII = /[#{s}]/
-
1
ESCAPE = /#{UNICODE}|\\[ -~#{s}]/
-
1
NMSTART = /[_a-zA-Z]|#{NONASCII}|#{ESCAPE}/
-
1
NMCHAR = /[a-zA-Z0-9_-]|#{NONASCII}|#{ESCAPE}/
-
1
STRING1 = /\"((?:[^\n\r\f\\"]|\\#{NL}|#{ESCAPE})*)\"/
-
1
STRING2 = /\'((?:[^\n\r\f\\']|\\#{NL}|#{ESCAPE})*)\'/
-
-
1
IDENT = /-?#{NMSTART}#{NMCHAR}*/
-
1
NAME = /#{NMCHAR}+/
-
1
NUM = /[0-9]+|[0-9]*\.[0-9]+/
-
1
STRING = /#{STRING1}|#{STRING2}/
-
1
URLCHAR = /[#%&*-~]|#{NONASCII}|#{ESCAPE}/
-
1
URL = /(#{URLCHAR}*)/
-
1
W = /[ \t\r\n\f]*/
-
1
VARIABLE = /(\$)(#{Sass::SCSS::RX::IDENT})/
-
-
# This is more liberal than the spec's definition,
-
# but that definition didn't work well with the greediness rules
-
1
RANGE = /(?:#{H}|\?){1,6}/
-
-
##
-
-
1
S = /[ \t\r\n\f]+/
-
-
1
COMMENT = /\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\//
-
1
SINGLE_LINE_COMMENT = /\/\/.*(\n[ \t]*\/\/.*)*/
-
-
1
CDO = quote("<!--")
-
1
CDC = quote("-->")
-
1
INCLUDES = quote("~=")
-
1
DASHMATCH = quote("|=")
-
1
PREFIXMATCH = quote("^=")
-
1
SUFFIXMATCH = quote("$=")
-
1
SUBSTRINGMATCH = quote("*=")
-
-
1
HASH = /##{NAME}/
-
-
1
IMPORTANT = /!#{W}important/i
-
1
DEFAULT = /!#{W}default/i
-
-
1
NUMBER = /#{NUM}(?:#{IDENT}|%)?/
-
-
1
URI = /url\(#{W}(?:#{STRING}|#{URL})#{W}\)/i
-
1
FUNCTION = /#{IDENT}\(/
-
-
1
UNICODERANGE = /u\+(?:#{H}{1,6}-#{H}{1,6}|#{RANGE})/i
-
-
# Defined in http://www.w3.org/TR/css3-selectors/#lex
-
1
PLUS = /#{W}\+/
-
1
GREATER = /#{W}>/
-
1
TILDE = /#{W}~/
-
1
NOT = quote(":not(", Regexp::IGNORECASE)
-
-
# Defined in https://developer.mozilla.org/en/CSS/@-moz-document as a
-
# non-standard version of http://www.w3.org/TR/css3-conditional/
-
1
URL_PREFIX = /url-prefix\(#{W}(?:#{STRING}|#{URL})#{W}\)/i
-
1
DOMAIN = /domain\(#{W}(?:#{STRING}|#{URL})#{W}\)/i
-
-
# Custom
-
1
HEXCOLOR = /\#[0-9a-fA-F]+/
-
1
INTERP_START = /#\{/
-
1
MOZ_ANY = quote(":-moz-any(", Regexp::IGNORECASE)
-
-
1
IDENT_HYPHEN_INTERP = /-(#\{)/
-
1
STRING1_NOINTERP = /\"((?:[^\n\r\f\\"#]|#(?!\{)|\\#{NL}|#{ESCAPE})*)\"/
-
1
STRING2_NOINTERP = /\'((?:[^\n\r\f\\'#]|#(?!\{)|\\#{NL}|#{ESCAPE})*)\'/
-
1
STRING_NOINTERP = /#{STRING1_NOINTERP}|#{STRING2_NOINTERP}/
-
# Can't use IDENT here, because it seems to take exponential time on 1.8.
-
# We could use it for 1.9 only, but I don't want to introduce a cross-version
-
# behavior difference.
-
# In any case, almost all CSS idents will be matched by this.
-
#
-
# We explicitly avoid parsing newlines or values/selectors longer than
-
# about 50 characters. This mitigates the problem of exponential parsing
-
# time when a value has a long string of valid, parsable content followed
-
# by something invalid.
-
1
STATIC_VALUE = /(-?#{NMSTART}|#{STRING_NOINTERP}|[ \t](?!%)|#[a-f0-9]|[,%]|#{NUM}|\!important){0,50}([;}])/i
-
1
STATIC_SELECTOR = /(#{NMCHAR}|[ \t]|[,>+*]|[:#.]#{NMSTART}){0,50}([{])/i
-
end
-
end
-
end
-
1
module Sass
-
1
module SCSS
-
# A subclass of {Parser} that parses code in Sass documents
-
# using some SCSS constructs.
-
# This is necessary because SassScript in Sass supports `!`-style variables,
-
# whereas in SCSS it doesn't.
-
1
class SassParser < Parser
-
1
@sass_script_parser = Sass::Script::Parser
-
end
-
end
-
end
-
1
module Sass
-
1
module SCSS
-
# A mixin for subclasses of {Sass::Script::Lexer}
-
# that makes them usable by {SCSS::Parser} to parse SassScript.
-
# In particular, the lexer doesn't support `!` for a variable prefix.
-
1
module ScriptLexer
-
1
private
-
-
1
def variable
-
return [:raw, "!important"] if scan(Sass::SCSS::RX::IMPORTANT)
-
_variable(Sass::SCSS::RX::VARIABLE)
-
end
-
end
-
end
-
end
-
1
module Sass
-
1
module SCSS
-
# A mixin for subclasses of {Sass::Script::Parser}
-
# that makes them usable by {SCSS::Parser} to parse SassScript.
-
# In particular, the parser won't raise an error
-
# when there's more content in the lexer once lexing is done.
-
# In addition, the parser doesn't support `!` for a variable prefix.
-
1
module ScriptParser
-
1
private
-
-
# @private
-
1
def lexer_class
-
klass = Class.new(super)
-
klass.send(:include, ScriptLexer)
-
klass
-
end
-
-
# Instead of raising an error when the parser is done,
-
# rewind the StringScanner so that it hasn't consumed the final token.
-
1
def assert_done
-
@lexer.unpeek!
-
end
-
end
-
end
-
end
-
1
module Sass
-
1
module SCSS
-
# A parser for a static SCSS tree.
-
# Parses with SCSS extensions, like nested rules and parent selectors,
-
# but without dynamic SassScript.
-
# This is useful for e.g. \{#parse\_selector parsing selectors}
-
# after resolving the interpolation.
-
1
class StaticParser < Parser
-
# Parses the text as a selector.
-
#
-
# @param filename [String, nil] The file in which the selector appears,
-
# or nil if there is no such file.
-
# Used for error reporting.
-
# @return [Selector::CommaSequence] The parsed selector
-
# @raise [Sass::SyntaxError] if there's a syntax error in the selector
-
1
def parse_selector
-
init_scanner!
-
seq = expr!(:selector_comma_sequence)
-
expected("selector") unless @scanner.eos?
-
seq.line = @line
-
seq.filename = @filename
-
seq
-
end
-
-
1
private
-
-
1
def variable; nil; end
-
1
def script_value; nil; end
-
1
def interpolation; nil; end
-
1
def interp_string; s = tok(STRING) and [s]; end
-
1
def interp_ident(ident = IDENT); s = tok(ident) and [s]; end
-
1
def use_css_import?; true; end
-
-
1
def special_directive(name)
-
return unless %w[media import charset -moz-document].include?(name)
-
super
-
end
-
end
-
end
-
end
-
1
require 'sass/selector/simple'
-
1
require 'sass/selector/abstract_sequence'
-
1
require 'sass/selector/comma_sequence'
-
1
require 'sass/selector/sequence'
-
1
require 'sass/selector/simple_sequence'
-
-
1
module Sass
-
# A namespace for nodes in the parse tree for selectors.
-
#
-
# {CommaSequence} is the toplevel seelctor,
-
# representing a comma-separated sequence of {Sequence}s,
-
# such as `foo bar, baz bang`.
-
# {Sequence} is the next level,
-
# representing {SimpleSequence}s separated by combinators (e.g. descendant or child),
-
# such as `foo bar` or `foo > bar baz`.
-
# {SimpleSequence} is a sequence of selectors that all apply to a single element,
-
# such as `foo.bar[attr=val]`.
-
# Finally, {Simple} is the superclass of the simplest selectors,
-
# such as `.foo` or `#bar`.
-
1
module Selector
-
# A parent-referencing selector (`&` in Sass).
-
# The function of this is to be replaced by the parent selector
-
# in the nested hierarchy.
-
1
class Parent < Simple
-
# @see Selector#to_a
-
1
def to_a
-
["&"]
-
end
-
-
# Always raises an exception.
-
#
-
# @raise [Sass::SyntaxError] Parent selectors should be resolved before unification
-
# @see Selector#unify
-
1
def unify(sels)
-
raise Sass::SyntaxError.new("[BUG] Cannot unify parent selectors.")
-
end
-
end
-
-
# A class selector (e.g. `.foo`).
-
1
class Class < Simple
-
# The class name.
-
#
-
# @return [Array<String, Sass::Script::Node>]
-
1
attr_reader :name
-
-
# @param name [Array<String, Sass::Script::Node>] The class name
-
1
def initialize(name)
-
@name = name
-
end
-
-
# @see Selector#to_a
-
1
def to_a
-
[".", *@name]
-
end
-
end
-
-
# An id selector (e.g. `#foo`).
-
1
class Id < Simple
-
# The id name.
-
#
-
# @return [Array<String, Sass::Script::Node>]
-
1
attr_reader :name
-
-
# @param name [Array<String, Sass::Script::Node>] The id name
-
1
def initialize(name)
-
@name = name
-
end
-
-
# @see Selector#to_a
-
1
def to_a
-
["#", *@name]
-
end
-
-
# Returns `nil` if `sels` contains an {Id} selector
-
# with a different name than this one.
-
#
-
# @see Selector#unify
-
1
def unify(sels)
-
return if sels.any? {|sel2| sel2.is_a?(Id) && self.name != sel2.name}
-
super
-
end
-
end
-
-
# A universal selector (`*` in CSS).
-
1
class Universal < Simple
-
# The selector namespace.
-
# `nil` means the default namespace,
-
# `[""]` means no namespace,
-
# `["*"]` means any namespace.
-
#
-
# @return [Array<String, Sass::Script::Node>, nil]
-
1
attr_reader :namespace
-
-
# @param namespace [Array<String, Sass::Script::Node>, nil] See \{#namespace}
-
1
def initialize(namespace)
-
@namespace = namespace
-
end
-
-
# @see Selector#to_a
-
1
def to_a
-
@namespace ? @namespace + ["|*"] : ["*"]
-
end
-
-
# Unification of a universal selector is somewhat complicated,
-
# especially when a namespace is specified.
-
# If there is no namespace specified
-
# or any namespace is specified (namespace `"*"`),
-
# then `sel` is returned without change
-
# (unless it's empty, in which case `"*"` is required).
-
#
-
# If a namespace is specified
-
# but `sel` does not specify a namespace,
-
# then the given namespace is applied to `sel`,
-
# either by adding this {Universal} selector
-
# or applying this namespace to an existing {Element} selector.
-
#
-
# If both this selector *and* `sel` specify namespaces,
-
# those namespaces are unified via {Simple#unify_namespaces}
-
# and the unified namespace is used, if possible.
-
#
-
# @todo There are lots of cases that this documentation specifies;
-
# make sure we thoroughly test **all of them**.
-
# @todo Keep track of whether a default namespace has been declared
-
# and handle namespace-unspecified selectors accordingly.
-
# @todo If any branch of a CommaSequence ends up being just `"*"`,
-
# then all other branches should be eliminated
-
#
-
# @see Selector#unify
-
1
def unify(sels)
-
name =
-
case sels.first
-
when Universal; :universal
-
when Element; sels.first.name
-
else
-
return [self] + sels unless namespace.nil? || namespace == ['*']
-
return sels unless sels.empty?
-
return [self]
-
end
-
-
ns, accept = unify_namespaces(namespace, sels.first.namespace)
-
return unless accept
-
[name == :universal ? Universal.new(ns) : Element.new(name, ns)] + sels[1..-1]
-
end
-
end
-
-
# An element selector (e.g. `h1`).
-
1
class Element < Simple
-
# The element name.
-
#
-
# @return [Array<String, Sass::Script::Node>]
-
1
attr_reader :name
-
-
# The selector namespace.
-
# `nil` means the default namespace,
-
# `[""]` means no namespace,
-
# `["*"]` means any namespace.
-
#
-
# @return [Array<String, Sass::Script::Node>, nil]
-
1
attr_reader :namespace
-
-
# @param name [Array<String, Sass::Script::Node>] The element name
-
# @param namespace [Array<String, Sass::Script::Node>, nil] See \{#namespace}
-
1
def initialize(name, namespace)
-
@name = name
-
@namespace = namespace
-
end
-
-
# @see Selector#to_a
-
1
def to_a
-
@namespace ? @namespace + ["|"] + @name : @name
-
end
-
-
# Unification of an element selector is somewhat complicated,
-
# especially when a namespace is specified.
-
# First, if `sel` contains another {Element} with a different \{#name},
-
# then the selectors can't be unified and `nil` is returned.
-
#
-
# Otherwise, if `sel` doesn't specify a namespace,
-
# or it specifies any namespace (via `"*"`),
-
# then it's returned with this element selector
-
# (e.g. `.foo` becomes `a.foo` or `svg|a.foo`).
-
# Similarly, if this selector doesn't specify a namespace,
-
# the namespace from `sel` is used.
-
#
-
# If both this selector *and* `sel` specify namespaces,
-
# those namespaces are unified via {Simple#unify_namespaces}
-
# and the unified namespace is used, if possible.
-
#
-
# @todo There are lots of cases that this documentation specifies;
-
# make sure we thoroughly test **all of them**.
-
# @todo Keep track of whether a default namespace has been declared
-
# and handle namespace-unspecified selectors accordingly.
-
#
-
# @see Selector#unify
-
1
def unify(sels)
-
case sels.first
-
when Universal;
-
when Element; return unless name == sels.first.name
-
else return [self] + sels
-
end
-
-
ns, accept = unify_namespaces(namespace, sels.first.namespace)
-
return unless accept
-
[Element.new(name, ns)] + sels[1..-1]
-
end
-
end
-
-
# Selector interpolation (`#{}` in Sass).
-
1
class Interpolation < Simple
-
# The script to run.
-
#
-
# @return [Sass::Script::Node]
-
1
attr_reader :script
-
-
# @param script [Sass::Script::Node] The script to run
-
1
def initialize(script)
-
@script = script
-
end
-
-
# @see Selector#to_a
-
1
def to_a
-
[@script]
-
end
-
-
# Always raises an exception.
-
#
-
# @raise [Sass::SyntaxError] Interpolation selectors should be resolved before unification
-
# @see Selector#unify
-
1
def unify(sels)
-
raise Sass::SyntaxError.new("[BUG] Cannot unify interpolation selectors.")
-
end
-
end
-
-
# An attribute selector (e.g. `[href^="http://"]`).
-
1
class Attribute < Simple
-
# The attribute name.
-
#
-
# @return [Array<String, Sass::Script::Node>]
-
1
attr_reader :name
-
-
# The attribute namespace.
-
# `nil` means the default namespace,
-
# `[""]` means no namespace,
-
# `["*"]` means any namespace.
-
#
-
# @return [Array<String, Sass::Script::Node>, nil]
-
1
attr_reader :namespace
-
-
# The matching operator, e.g. `"="` or `"^="`.
-
#
-
# @return [String]
-
1
attr_reader :operator
-
-
# The right-hand side of the operator.
-
#
-
# @return [Array<String, Sass::Script::Node>]
-
1
attr_reader :value
-
-
# @param name [Array<String, Sass::Script::Node>] The attribute name
-
# @param namespace [Array<String, Sass::Script::Node>, nil] See \{#namespace}
-
# @param operator [String] The matching operator, e.g. `"="` or `"^="`
-
# @param value [Array<String, Sass::Script::Node>] See \{#value}
-
1
def initialize(name, namespace, operator, value)
-
@name = name
-
@namespace = namespace
-
@operator = operator
-
@value = value
-
end
-
-
# @see Selector#to_a
-
1
def to_a
-
res = ["["]
-
res.concat(@namespace) << "|" if @namespace
-
res.concat @name
-
(res << @operator).concat @value if @value
-
res << "]"
-
end
-
end
-
-
# A pseudoclass (e.g. `:visited`) or pseudoelement (e.g. `::first-line`) selector.
-
# It can have arguments (e.g. `:nth-child(2n+1)`).
-
1
class Pseudo < Simple
-
# The type of the selector.
-
# `:class` if this is a pseudoclass selector,
-
# `:element` if it's a pseudoelement.
-
#
-
# @return [Symbol]
-
1
attr_reader :type
-
-
# Some psuedo-class-syntax selectors (`:after` and `:before)
-
# are actually considered pseudo-elements
-
# and must be at the end of the selector to function properly.
-
#
-
# @return [Array<String>]
-
1
FINAL_SELECTORS = %w[after before]
-
-
# The name of the selector.
-
#
-
# @return [Array<String, Sass::Script::Node>]
-
1
attr_reader :name
-
-
# The argument to the selector,
-
# or `nil` if no argument was given.
-
#
-
# This may include SassScript nodes that will be run during resolution.
-
# Note that this should not include SassScript nodes
-
# after resolution has taken place.
-
#
-
# @return [Array<String, Sass::Script::Node>, nil]
-
1
attr_reader :arg
-
-
# @param type [Symbol] See \{#type}
-
# @param name [Array<String, Sass::Script::Node>] The name of the selector
-
# @param arg [nil, Array<String, Sass::Script::Node>] The argument to the selector,
-
# or nil if no argument was given
-
1
def initialize(type, name, arg)
-
@type = type
-
@name = name
-
@arg = arg
-
end
-
-
1
def final?
-
type == :class && FINAL_SELECTORS.include?(name.first)
-
end
-
-
# @see Selector#to_a
-
1
def to_a
-
res = [@type == :class ? ":" : "::"] + @name
-
(res << "(").concat(Sass::Util.strip_string_array(@arg)) << ")" if @arg
-
res
-
end
-
-
# Returns `nil` if this is a pseudoelement selector
-
# and `sels` contains a pseudoelement selector different than this one.
-
#
-
# @see Selector#unify
-
1
def unify(sels)
-
return if type == :element && sels.any? do |sel|
-
sel.is_a?(Pseudo) && sel.type == :element &&
-
(sel.name != self.name || sel.arg != self.arg)
-
end
-
return sels + [self] if final?
-
super
-
end
-
end
-
-
# A pseudoclass selector whose argument is itself a selector
-
# (e.g. `:not(.foo)` or `:-moz-all(.foo, .bar)`).
-
1
class SelectorPseudoClass < Simple
-
# The name of the pseudoclass.
-
#
-
# @return [String]
-
1
attr_reader :name
-
-
# The selector argument.
-
#
-
# @return [Selector::Sequence]
-
1
attr_reader :selector
-
-
# @param [String] The name of the pseudoclass
-
# @param [Selector::CommaSequence] The selector argument
-
1
def initialize(name, selector)
-
@name = name
-
@selector = selector
-
end
-
-
# @see Selector#to_a
-
1
def to_a
-
[":", @name, "("] + @selector.to_a + [")"]
-
end
-
end
-
end
-
end
-
1
module Sass
-
1
module Selector
-
# The abstract parent class of the various selector sequence classes.
-
#
-
# All subclasses should implement a `members` method that returns an array
-
# of object that respond to `#line=` and `#filename=`, as well as a `to_a`
-
# method that returns an array of strings and script nodes.
-
1
class AbstractSequence
-
# The line of the Sass template on which this selector was declared.
-
#
-
# @return [Fixnum]
-
1
attr_reader :line
-
-
# The name of the file in which this selector was declared.
-
#
-
# @return [String, nil]
-
1
attr_reader :filename
-
-
# Sets the line of the Sass template on which this selector was declared.
-
# This also sets the line for all child selectors.
-
#
-
# @param line [Fixnum]
-
# @return [Fixnum]
-
1
def line=(line)
-
members.each {|m| m.line = line}
-
@line = line
-
end
-
-
# Sets the name of the file in which this selector was declared,
-
# or `nil` if it was not declared in a file (e.g. on stdin).
-
# This also sets the filename for all child selectors.
-
#
-
# @param filename [String, nil]
-
# @return [String, nil]
-
1
def filename=(filename)
-
members.each {|m| m.filename = filename}
-
@filename = filename
-
end
-
-
# Returns a hash code for this sequence.
-
#
-
# Subclasses should define `#_hash` rather than overriding this method,
-
# which automatically handles memoizing the result.
-
#
-
# @return [Fixnum]
-
1
def hash
-
@_hash ||= _hash
-
end
-
-
# Checks equality between this and another object.
-
#
-
# Subclasses should define `#_eql?` rather than overriding this method,
-
# which handles checking class equality and hash equality.
-
#
-
# @param other [Object] The object to test equality against
-
# @return [Boolean] Whether or not this is equal to `other`
-
1
def eql?(other)
-
other.class == self.class && other.hash == self.hash && _eql?(other)
-
end
-
1
alias_method :==, :eql?
-
-
# Converts the selector into a string. This is the standard selector
-
# string, along with any SassScript interpolation that may exist.
-
#
-
# @return [String]
-
1
def to_s
-
to_a.map {|e| e.is_a?(Sass::Script::Node) ? "\#{#{e.to_sass}}" : e}.join
-
end
-
end
-
end
-
end
-
1
module Sass
-
1
module Selector
-
# A comma-separated sequence of selectors.
-
1
class CommaSequence < AbstractSequence
-
# The comma-separated selector sequences
-
# represented by this class.
-
#
-
# @return [Array<Sequence>]
-
1
attr_reader :members
-
-
# @param seqs [Array<Sequence>] See \{#members}
-
1
def initialize(seqs)
-
@members = seqs
-
end
-
-
# Resolves the {Parent} selectors within this selector
-
# by replacing them with the given parent selector,
-
# handling commas appropriately.
-
#
-
# @param super_cseq [CommaSequence] The parent selector
-
# @return [CommaSequence] This selector, with parent references resolved
-
# @raise [Sass::SyntaxError] If a parent selector is invalid
-
1
def resolve_parent_refs(super_cseq)
-
if super_cseq.nil?
-
if @members.any? do |sel|
-
sel.members.any? do |sel_or_op|
-
sel_or_op.is_a?(SimpleSequence) && sel_or_op.members.any? {|ssel| ssel.is_a?(Parent)}
-
end
-
end
-
raise Sass::SyntaxError.new("Base-level rules cannot contain the parent-selector-referencing character '&'.")
-
end
-
return self
-
end
-
-
CommaSequence.new(
-
super_cseq.members.map do |super_seq|
-
@members.map {|seq| seq.resolve_parent_refs(super_seq)}
-
end.flatten)
-
end
-
-
# Non-destrucively extends this selector with the extensions specified in a hash
-
# (which should come from {Sass::Tree::Visitors::Cssize}).
-
#
-
# @todo Link this to the reference documentation on `@extend`
-
# when such a thing exists.
-
#
-
# @param extends [Sass::Util::SubsetMap{Selector::Simple => Selector::Sequence}]
-
# The extensions to perform on this selector
-
# @return [CommaSequence] A copy of this selector,
-
# with extensions made according to `extends`
-
1
def do_extend(extends)
-
CommaSequence.new(members.map {|seq| seq.do_extend(extends)}.flatten)
-
end
-
-
# Returns a string representation of the sequence.
-
# This is basically the selector string.
-
#
-
# @return [String]
-
1
def inspect
-
members.map {|m| m.inspect}.join(", ")
-
end
-
-
# @see Simple#to_a
-
1
def to_a
-
arr = Sass::Util.intersperse(@members.map {|m| m.to_a}, ", ").flatten
-
arr.delete("\n")
-
arr
-
end
-
-
1
private
-
-
1
def _hash
-
members.hash
-
end
-
-
1
def _eql?(other)
-
other.class == self.class && other.members.eql?(self.members)
-
end
-
end
-
end
-
end
-
1
module Sass
-
1
module Selector
-
# An operator-separated sequence of
-
# {SimpleSequence simple selector sequences}.
-
1
class Sequence < AbstractSequence
-
# Sets the line of the Sass template on which this selector was declared.
-
# This also sets the line for all child selectors.
-
#
-
# @param line [Fixnum]
-
# @return [Fixnum]
-
1
def line=(line)
-
members.each {|m| m.line = line if m.is_a?(SimpleSequence)}
-
line
-
end
-
-
# Sets the name of the file in which this selector was declared,
-
# or `nil` if it was not declared in a file (e.g. on stdin).
-
# This also sets the filename for all child selectors.
-
#
-
# @param filename [String, nil]
-
# @return [String, nil]
-
1
def filename=(filename)
-
members.each {|m| m.filename = filename if m.is_a?(SimpleSequence)}
-
filename
-
end
-
-
# The array of {SimpleSequence simple selector sequences}, operators, and newlines.
-
# The operators are strings such as `"+"` and `">"`
-
# representing the corresponding CSS operators.
-
# Newlines are also newline strings;
-
# these aren't semantically relevant,
-
# but they do affect formatting.
-
#
-
# @return [Array<SimpleSequence, String>]
-
1
attr_reader :members
-
-
# @param seqs_and_ops [Array<SimpleSequence, String>] See \{#members}
-
1
def initialize(seqs_and_ops)
-
@members = seqs_and_ops
-
end
-
-
# Resolves the {Parent} selectors within this selector
-
# by replacing them with the given parent selector,
-
# handling commas appropriately.
-
#
-
# @param super_seq [Sequence] The parent selector sequence
-
# @return [Sequence] This selector, with parent references resolved
-
# @raise [Sass::SyntaxError] If a parent selector is invalid
-
1
def resolve_parent_refs(super_seq)
-
members = @members
-
nl = (members.first == "\n" && members.shift)
-
unless members.any? do |seq_or_op|
-
seq_or_op.is_a?(SimpleSequence) && seq_or_op.members.first.is_a?(Parent)
-
end
-
members = []
-
members << nl if nl
-
members << SimpleSequence.new([Parent.new])
-
members += @members
-
end
-
-
Sequence.new(
-
members.map do |seq_or_op|
-
next seq_or_op unless seq_or_op.is_a?(SimpleSequence)
-
seq_or_op.resolve_parent_refs(super_seq)
-
end.flatten)
-
end
-
-
# Non-destructively extends this selector with the extensions specified in a hash
-
# (which should come from {Sass::Tree::Visitors::Cssize}).
-
#
-
# @overload def do_extend(extends)
-
# @param extends [Sass::Util::SubsetMap{Selector::Simple => Selector::Sequence}]
-
# The extensions to perform on this selector
-
# @return [Array<Sequence>] A list of selectors generated
-
# by extending this selector with `extends`.
-
# These correspond to a {CommaSequence}'s {CommaSequence#members members array}.
-
# @see CommaSequence#do_extend
-
1
def do_extend(extends, seen = Set.new)
-
paths = Sass::Util.paths(members.map do |sseq_or_op|
-
next [[sseq_or_op]] unless sseq_or_op.is_a?(SimpleSequence)
-
extended = sseq_or_op.do_extend(extends, seen)
-
choices = extended.map {|seq| seq.members}
-
choices.unshift([sseq_or_op]) unless extended.any? {|seq| seq.superselector?(sseq_or_op)}
-
choices
-
end)
-
Sass::Util.flatten(paths.map {|path| weave(path)}, 1).map {|p| Sequence.new(p)}
-
end
-
-
# Returns whether or not this selector matches all elements
-
# that the given selector matches (as well as possibly more).
-
#
-
# @example
-
# (.foo).superselector?(.foo.bar) #=> true
-
# (.foo).superselector?(.bar) #=> false
-
# (.bar .foo).superselector?(.foo) #=> false
-
# @param sseq [SimpleSequence]
-
# @return [Boolean]
-
1
def superselector?(sseq)
-
return false unless members.size == 1
-
members.last.superselector?(sseq)
-
end
-
-
# @see Simple#to_a
-
1
def to_a
-
ary = @members.map {|seq_or_op| seq_or_op.is_a?(SimpleSequence) ? seq_or_op.to_a : seq_or_op}
-
Sass::Util.intersperse(ary, " ").flatten.compact
-
end
-
-
# Returns a string representation of the sequence.
-
# This is basically the selector string.
-
#
-
# @return [String]
-
1
def inspect
-
members.map {|m| m.inspect}.join(" ")
-
end
-
-
1
private
-
-
# Conceptually, this expands "parenthesized selectors".
-
# That is, if we have `.A .B {@extend .C}` and `.D .C {...}`,
-
# this conceptually expands into `.D .C, .D (.A .B)`,
-
# and this function translates `.D (.A .B)` into `.D .A .B, .A.D .B, .D .A .B`.
-
#
-
# @param path [Array<Array<SimpleSequence or String>>] A list of parenthesized selector groups.
-
# @return [Array<Array<SimpleSequence or String>>] A list of fully-expanded selectors.
-
1
def weave(path)
-
befores = [[]]
-
afters = path.dup
-
-
until afters.empty?
-
current = afters.shift.dup
-
last_current = [current.pop]
-
while !current.empty? && last_current.first.is_a?(String) || current.last.is_a?(String)
-
last_current.unshift(current.pop)
-
end
-
befores = Sass::Util.flatten(befores.map do |before|
-
next [] unless sub = subweave(before, current)
-
sub.map {|seqs| seqs + last_current}
-
end, 1)
-
end
-
return befores
-
end
-
-
# This interweaves two lists of selectors,
-
# returning all possible orderings of them (including using unification)
-
# that maintain the relative ordering of the input arrays.
-
#
-
# For example, given `.foo .bar` and `.baz .bang`,
-
# this would return `.foo .bar .baz .bang`, `.foo .bar.baz .bang`,
-
# `.foo .baz .bar .bang`, `.foo .baz .bar.bang`, `.foo .baz .bang .bar`,
-
# and so on until `.baz .bang .foo .bar`.
-
#
-
# @param seq1 [Array<SimpleSequence or String>]
-
# @param seq2 [Array<SimpleSequence or String>]
-
# @return [Array<Array<SimpleSequence or String>>]
-
1
def subweave(seq1, seq2)
-
return [seq2] if seq1.empty?
-
return [seq1] if seq2.empty?
-
-
return unless init = merge_initial_ops(seq1, seq2)
-
seq1 = group_selectors(seq1)
-
seq2 = group_selectors(seq2)
-
lcs = Sass::Util.lcs(seq2, seq1) do |s1, s2|
-
next s1 if s1 == s2
-
next unless s1.first.is_a?(SimpleSequence) && s2.first.is_a?(SimpleSequence)
-
next s2 if subweave_superselector?(s1, s2)
-
next s1 if subweave_superselector?(s2, s1)
-
end
-
-
diff = [[init]]
-
until lcs.empty?
-
diff << chunks(seq1, seq2) {|s| subweave_superselector?(s.first, lcs.first)} << [lcs.shift]
-
seq1.shift
-
seq2.shift
-
end
-
diff << chunks(seq1, seq2) {|s| s.empty?}
-
diff.reject! {|c| c.empty?}
-
-
Sass::Util.paths(diff).map {|p| p.flatten}
-
end
-
-
# Extracts initial selector operators (`"+"`, `">"`, `"~"`, and `"\n"`)
-
# from two sequences and merges them together into a single array of
-
# selector operators.
-
#
-
# @param seq1 [Array<SimpleSequence or String>]
-
# @param seq2 [Array<SimpleSequence or String>]
-
# @return [Array<String>, nil] If there are no operators in the merged
-
# sequence, this will be the empty array. If the operators cannot be
-
# merged, this will be nil.
-
1
def merge_initial_ops(seq1, seq2)
-
ops1, ops2 = [], []
-
ops1 << seq1.shift while seq1.first.is_a?(String)
-
ops2 << seq2.shift while seq2.first.is_a?(String)
-
-
newline = false
-
newline ||= !!ops1.shift if ops1.first == "\n"
-
newline ||= !!ops2.shift if ops2.first == "\n"
-
-
# If neither sequence is a subsequence of the other, they cannot be
-
# merged successfully
-
lcs = Sass::Util.lcs(ops1, ops2)
-
return unless lcs == ops1 || lcs == ops2
-
return (newline ? ["\n"] : []) + (ops1.size > ops2.size ? ops1 : ops2)
-
end
-
-
# Takes initial subsequences of `seq1` and `seq2` and returns all
-
# orderings of those subsequences. The initial subsequences are determined
-
# by a block.
-
#
-
# Destructively removes the initial subsequences of `seq1` and `seq2`.
-
#
-
# For example, given `(A B C | D E)` and `(1 2 | 3 4 5)` (with `|`
-
# denoting the boundary of the initial subsequence), this would return
-
# `[(A B C 1 2), (1 2 A B C)]`. The sequences would then be `(D E)` and
-
# `(3 4 5)`.
-
#
-
# @param seq1 [Array]
-
# @param seq2 [Array]
-
# @yield [a] Used to determine when to cut off the initial subsequences.
-
# Called repeatedly for each sequence until it returns true.
-
# @yieldparam a [Array] A final subsequence of one input sequence after
-
# cutting off some initial subsequence.
-
# @yieldreturn [Boolean] Whether or not to cut off the initial subsequence
-
# here.
-
1
def chunks(seq1, seq2)
-
chunk1 = []
-
chunk1 << seq1.shift until yield seq1
-
chunk2 = []
-
chunk2 << seq2.shift until yield seq2
-
return [] if chunk1.empty? && chunk2.empty?
-
return [chunk2] if chunk1.empty?
-
return [chunk1] if chunk2.empty?
-
[chunk1 + chunk2, chunk2 + chunk1]
-
end
-
-
# Groups a sequence into subsequences. The subsequences are determined by
-
# strings; adjacent non-string elements will be put into separate groups,
-
# but any element adjacent to a string will be grouped with that string.
-
#
-
# For example, `(A B "C" D E "F" G "H" "I" J)` will become `[(A) (B "C" D)
-
# (E "F" G "H" "I" J)]`.
-
#
-
# @param seq [Array]
-
# @return [Array<Array>]
-
1
def group_selectors(seq)
-
newseq = []
-
tail = seq.dup
-
until tail.empty?
-
head = []
-
begin
-
head << tail.shift
-
end while !tail.empty? && head.last.is_a?(String) || tail.first.is_a?(String)
-
newseq << head
-
end
-
return newseq
-
end
-
-
# Given two sequences of simple selectors, returns whether `sseq1` is a
-
# superselector of `sseq2`.
-
#
-
# @param sseq1 [Array<SimpleSelector or String>]
-
# @param sseq2 [Array<SimpleSelector or String>]
-
# @return [Boolean]
-
1
def subweave_superselector?(sseq1, sseq2)
-
if sseq1.size > 1
-
# More complex selectors are never superselectors of less complex ones
-
return unless sseq2.size > 1
-
# .foo ~ .bar is a superselector of .foo + .bar
-
return unless sseq1[1] == "~" ? sseq2[1] != ">" : sseq2[1] == sseq1[1]
-
return unless sseq1.first.superselector?(sseq2.first)
-
return true if sseq1.size == 2
-
return false if sseq2.size == 2
-
return subweave_superselector?(sseq1[2..-1], sseq2[2..-1])
-
elsif sseq2.size > 1
-
return true if sseq2[1] == ">" && sseq1.first.superselector?(sseq2.first)
-
return false if sseq2.size == 2
-
return subweave_superselector?(sseq1, sseq2[2..-1])
-
else
-
sseq1.first.superselector?(sseq2.first)
-
end
-
end
-
-
1
def _hash
-
members.reject {|m| m == "\n"}.hash
-
end
-
-
1
def _eql?(other)
-
other.members.reject {|m| m == "\n"}.eql?(self.members.reject {|m| m == "\n"})
-
end
-
end
-
end
-
end
-
1
module Sass
-
1
module Selector
-
# The abstract superclass for simple selectors
-
# (that is, those that don't compose multiple selectors).
-
1
class Simple
-
# The line of the Sass template on which this selector was declared.
-
#
-
# @return [Fixnum]
-
1
attr_accessor :line
-
-
# The name of the file in which this selector was declared,
-
# or `nil` if it was not declared in a file (e.g. on stdin).
-
#
-
# @return [String, nil]
-
1
attr_accessor :filename
-
-
# Returns a representation of the node
-
# as an array of strings and potentially {Sass::Script::Node}s
-
# (if there's interpolation in the selector).
-
# When the interpolation is resolved and the strings are joined together,
-
# this will be the string representation of this node.
-
#
-
# @return [Array<String, Sass::Script::Node>]
-
1
def to_a
-
Sass::Util.abstract(self)
-
end
-
-
# Returns a string representation of the node.
-
# This is basically the selector string.
-
#
-
# @return [String]
-
1
def inspect
-
to_a.map {|e| e.is_a?(Sass::Script::Node) ? "\#{#{e.to_sass}}" : e}.join
-
end
-
-
# @see \{#inspect}
-
# @return [String]
-
1
def to_s
-
inspect
-
end
-
-
# Returns a hash code for this selector object.
-
#
-
# By default, this is based on the value of \{#to\_a},
-
# so if that contains information irrelevant to the identity of the selector,
-
# this should be overridden.
-
#
-
# @return [Fixnum]
-
1
def hash
-
@_hash ||= to_a.hash
-
end
-
-
# Checks equality between this and another object.
-
#
-
# By default, this is based on the value of \{#to\_a},
-
# so if that contains information irrelevant to the identity of the selector,
-
# this should be overridden.
-
#
-
# @param other [Object] The object to test equality against
-
# @return [Boolean] Whether or not this is equal to `other`
-
1
def eql?(other)
-
other.class == self.class && other.hash == self.hash && other.to_a.eql?(to_a)
-
end
-
1
alias_method :==, :eql?
-
-
# Unifies this selector with a {SimpleSequence}'s {SimpleSequence#members members array},
-
# returning another `SimpleSequence` members array
-
# that matches both this selector and the input selector.
-
#
-
# By default, this just appends this selector to the end of the array
-
# (or returns the original array if this selector already exists in it).
-
#
-
# @param sels [Array<Simple>] A {SimpleSequence}'s {SimpleSequence#members members array}
-
# @return [Array<Simple>, nil] A {SimpleSequence} {SimpleSequence#members members array}
-
# matching both `sels` and this selector,
-
# or `nil` if this is impossible (e.g. unifying `#foo` and `#bar`)
-
# @raise [Sass::SyntaxError] If this selector cannot be unified.
-
# This will only ever occur when a dynamic selector,
-
# such as {Parent} or {Interpolation}, is used in unification.
-
# Since these selectors should be resolved
-
# by the time extension and unification happen,
-
# this exception will only ever be raised as a result of programmer error
-
1
def unify(sels)
-
return sels if sels.any? {|sel2| eql?(sel2)}
-
sels_with_ix = Sass::Util.enum_with_index(sels)
-
_, i =
-
if self.is_a?(Pseudo) || self.is_a?(SelectorPseudoClass)
-
sels_with_ix.find {|sel, _| sel.is_a?(Pseudo) && (sels.last.final? || sels.last.type == :element)}
-
else
-
sels_with_ix.find {|sel, _| sel.is_a?(Pseudo) || sel.is_a?(SelectorPseudoClass)}
-
end
-
return sels + [self] unless i
-
return sels[0...i] + [self] + sels[i..-1]
-
end
-
-
1
protected
-
-
# Unifies two namespaces,
-
# returning a namespace that works for both of them if possible.
-
#
-
# @param ns1 [String, nil] The first namespace.
-
# `nil` means none specified, e.g. `foo`.
-
# The empty string means no namespace specified, e.g. `|foo`.
-
# `"*"` means any namespace is allowed, e.g. `*|foo`.
-
# @param ns2 [String, nil] The second namespace. See `ns1`.
-
# @return [Array(String or nil, Boolean)]
-
# The first value is the unified namespace, or `nil` for no namespace.
-
# The second value is whether or not a namespace that works for both inputs
-
# could be found at all.
-
# If the second value is `false`, the first should be ignored.
-
1
def unify_namespaces(ns1, ns2)
-
return nil, false unless ns1 == ns2 || ns1.nil? || ns1 == ['*'] || ns2.nil? || ns2 == ['*']
-
return ns2, true if ns1 == ['*']
-
return ns1, true if ns2 == ['*']
-
return ns1 || ns2, true
-
end
-
end
-
end
-
end
-
1
module Sass
-
1
module Selector
-
# A unseparated sequence of selectors
-
# that all apply to a single element.
-
# For example, `.foo#bar[attr=baz]` is a simple sequence
-
# of the selectors `.foo`, `#bar`, and `[attr=baz]`.
-
1
class SimpleSequence < AbstractSequence
-
# The array of individual selectors.
-
#
-
# @return [Array<Simple>]
-
1
attr_reader :members
-
-
# Returns the element or universal selector in this sequence,
-
# if it exists.
-
#
-
# @return [Element, Universal, nil]
-
1
def base
-
@base ||= (members.first if members.first.is_a?(Element) || members.first.is_a?(Universal))
-
end
-
-
# Returns the non-base selectors in this sequence.
-
#
-
# @return [Set<Simple>]
-
1
def rest
-
@rest ||= Set.new(base ? members[1..-1] : members)
-
end
-
-
# @param selectors [Array<Simple>] See \{#members}
-
1
def initialize(selectors)
-
@members = selectors
-
end
-
-
# Resolves the {Parent} selectors within this selector
-
# by replacing them with the given parent selector,
-
# handling commas appropriately.
-
#
-
# @param super_seq [Sequence] The parent selector sequence
-
# @return [Array<SimpleSequence>] This selector, with parent references resolved.
-
# This is an array because the parent selector is itself a {Sequence}
-
# @raise [Sass::SyntaxError] If a parent selector is invalid
-
1
def resolve_parent_refs(super_seq)
-
# Parent selector only appears as the first selector in the sequence
-
return [self] unless @members.first.is_a?(Parent)
-
-
return super_seq.members if @members.size == 1
-
unless super_seq.members.last.is_a?(SimpleSequence)
-
raise Sass::SyntaxError.new("Invalid parent selector: " + super_seq.to_a.join)
-
end
-
-
super_seq.members[0...-1] +
-
[SimpleSequence.new(super_seq.members.last.members + @members[1..-1])]
-
end
-
-
# Non-destrucively extends this selector with the extensions specified in a hash
-
# (which should come from {Sass::Tree::Visitors::Cssize}).
-
#
-
# @overload def do_extend(extends)
-
# @param extends [{Selector::Simple => Selector::Sequence}]
-
# The extensions to perform on this selector
-
# @return [Array<Sequence>] A list of selectors generated
-
# by extending this selector with `extends`.
-
# @see CommaSequence#do_extend
-
1
def do_extend(extends, seen = Set.new)
-
extends.get(members.to_set).map do |seq, sels|
-
# If A {@extend B} and C {...},
-
# seq is A, sels is B, and self is C
-
-
self_without_sel = self.members - sels
-
next unless unified = seq.members.last.unify(self_without_sel)
-
[sels, seq.members[0...-1] + [unified]]
-
end.compact.map do |sels, seq|
-
seq = Sequence.new(seq)
-
seen.include?(sels) ? [] : seq.do_extend(extends, seen + [sels])
-
end.flatten.uniq
-
end
-
-
# Unifies this selector with another {SimpleSequence}'s {SimpleSequence#members members array},
-
# returning another `SimpleSequence`
-
# that matches both this selector and the input selector.
-
#
-
# @param sels [Array<Simple>] A {SimpleSequence}'s {SimpleSequence#members members array}
-
# @return [SimpleSequence, nil] A {SimpleSequence} matching both `sels` and this selector,
-
# or `nil` if this is impossible (e.g. unifying `#foo` and `#bar`)
-
# @raise [Sass::SyntaxError] If this selector cannot be unified.
-
# This will only ever occur when a dynamic selector,
-
# such as {Parent} or {Interpolation}, is used in unification.
-
# Since these selectors should be resolved
-
# by the time extension and unification happen,
-
# this exception will only ever be raised as a result of programmer error
-
1
def unify(sels)
-
return unless sseq = members.inject(sels) do |sseq, sel|
-
return unless sseq
-
sel.unify(sseq)
-
end
-
SimpleSequence.new(sseq)
-
end
-
-
# Returns whether or not this selector matches all elements
-
# that the given selector matches (as well as possibly more).
-
#
-
# @example
-
# (.foo).superselector?(.foo.bar) #=> true
-
# (.foo).superselector?(.bar) #=> false
-
# @param sseq [SimpleSequence]
-
# @return [Boolean]
-
1
def superselector?(sseq)
-
(base.nil? || base.eql?(sseq.base)) && rest.subset?(sseq.rest)
-
end
-
-
# @see Simple#to_a
-
1
def to_a
-
@members.map {|sel| sel.to_a}.flatten
-
end
-
-
# Returns a string representation of the sequence.
-
# This is basically the selector string.
-
#
-
# @return [String]
-
1
def inspect
-
members.map {|m| m.inspect}.join
-
end
-
-
1
private
-
-
1
def _hash
-
[base, Sass::Util.set_hash(rest)].hash
-
end
-
-
1
def _eql?(other)
-
other.base.eql?(self.base) && Sass::Util.set_eql?(other.rest, self.rest)
-
end
-
end
-
end
-
end
-
1
module Sass
-
# This module contains functionality that's shared between Haml and Sass.
-
1
module Shared
-
1
extend self
-
-
# Scans through a string looking for the interoplation-opening `#{`
-
# and, when it's found, yields the scanner to the calling code
-
# so it can handle it properly.
-
#
-
# The scanner will have any backslashes immediately in front of the `#{`
-
# as the second capture group (`scan[2]`),
-
# and the text prior to that as the first (`scan[1]`).
-
#
-
# @yieldparam scan [StringScanner] The scanner scanning through the string
-
# @return [String] The text remaining in the scanner after all `#{`s have been processed
-
1
def handle_interpolation(str)
-
scan = Sass::Util::MultibyteStringScanner.new(str)
-
yield scan while scan.scan(/(.*?)(\\*)\#\{/m)
-
scan.rest
-
end
-
-
# Moves a scanner through a balanced pair of characters.
-
# For example:
-
#
-
# Foo (Bar (Baz bang) bop) (Bang (bop bip))
-
# ^ ^
-
# from to
-
#
-
# @param scanner [StringScanner] The string scanner to move
-
# @param start [Character] The character opening the balanced pair.
-
# A `Fixnum` in 1.8, a `String` in 1.9
-
# @param finish [Character] The character closing the balanced pair.
-
# A `Fixnum` in 1.8, a `String` in 1.9
-
# @param count [Fixnum] The number of opening characters matched
-
# before calling this method
-
# @return [(String, String)] The string matched within the balanced pair
-
# and the rest of the string.
-
# `["Foo (Bar (Baz bang) bop)", " (Bang (bop bip))"]` in the example above.
-
1
def balance(scanner, start, finish, count = 0)
-
str = ''
-
scanner = Sass::Util::MultibyteStringScanner.new(scanner) unless scanner.is_a? StringScanner
-
regexp = Regexp.new("(.*?)[\\#{start.chr}\\#{finish.chr}]", Regexp::MULTILINE)
-
while scanner.scan(regexp)
-
str << scanner.matched
-
count += 1 if scanner.matched[-1] == start
-
count -= 1 if scanner.matched[-1] == finish
-
return [str.strip, scanner.rest] if count == 0
-
end
-
end
-
-
# Formats a string for use in error messages about indentation.
-
#
-
# @param indentation [String] The string used for indentation
-
# @param was [Boolean] Whether or not to add `"was"` or `"were"`
-
# (depending on how many characters were in `indentation`)
-
# @return [String] The name of the indentation (e.g. `"12 spaces"`, `"1 tab"`)
-
1
def human_indentation(indentation, was = false)
-
if !indentation.include?(?\t)
-
noun = 'space'
-
elsif !indentation.include?(?\s)
-
noun = 'tab'
-
else
-
return indentation.inspect + (was ? ' was' : '')
-
end
-
-
singular = indentation.length == 1
-
if was
-
was = singular ? ' was' : ' were'
-
else
-
was = ''
-
end
-
-
"#{indentation.length} #{noun}#{'s' unless singular}#{was}"
-
end
-
end
-
end
-
1
module Sass::Tree
-
# A static node representing an unproccessed Sass `@charset` directive.
-
#
-
# @see Sass::Tree
-
1
class CharsetNode < Node
-
# The name of the charset.
-
#
-
# @return [String]
-
1
attr_accessor :name
-
-
# @param name [String] see \{#name}
-
1
def initialize(name)
-
@name = name
-
super()
-
end
-
-
# @see Node#invisible?
-
1
def invisible?
-
!Sass::Util.ruby1_8?
-
end
-
end
-
end
-
1
require 'sass/tree/node'
-
-
1
module Sass::Tree
-
# A static node representing a Sass comment (silent or loud).
-
#
-
# @see Sass::Tree
-
1
class CommentNode < Node
-
# The text of the comment, not including `/*` and `*/`.
-
# Interspersed with {Sass::Script::Node}s representing `#{}`-interpolation
-
# if this is a loud comment.
-
#
-
# @return [Array<String, Sass::Script::Node>]
-
1
attr_accessor :value
-
-
# The text of the comment
-
# after any interpolated SassScript has been resolved.
-
# Only set once \{Tree::Visitors::Perform} has been run.
-
#
-
# @return [String]
-
1
attr_accessor :resolved_value
-
-
# Whether the comment is loud.
-
#
-
# Loud comments start with ! and force the comment to be generated
-
# irrespective of compilation settings or the comment syntax used.
-
#
-
# @return [Boolean]
-
1
attr_accessor :loud
-
-
# Whether or not the comment is silent (that is, doesn't output to CSS).
-
#
-
# @return [Boolean]
-
1
attr_accessor :silent
-
-
# @param value [Array<String, Sass::Script::Node>] See \{#value}
-
# @param silent [Boolean] See \{#silent}
-
# @param loud [Boolean] See \{#loud}
-
1
def initialize(value, silent, loud)
-
@value = Sass::Util.with_extracted_values(value) {|str| normalize_indentation str}
-
@silent = silent
-
@loud = loud
-
super()
-
end
-
-
# Compares the contents of two comments.
-
#
-
# @param other [Object] The object to compare with
-
# @return [Boolean] Whether or not this node and the other object
-
# are the same
-
1
def ==(other)
-
self.class == other.class && value == other.value && silent == other.silent && loud == other.loud
-
end
-
-
# Returns `true` if this is a silent comment
-
# or the current style doesn't render comments.
-
#
-
# Comments starting with ! are never invisible (and the ! is removed from the output.)
-
#
-
# @return [Boolean]
-
1
def invisible?
-
if @loud
-
return false
-
else
-
@silent || (style == :compressed)
-
end
-
end
-
-
# Returns the number of lines in the comment.
-
#
-
# @return [Fixnum]
-
1
def lines
-
@value.inject(0) do |s, e|
-
next s + e.count("\n") if e.is_a?(String)
-
next s
-
end
-
end
-
-
1
private
-
-
1
def normalize_indentation(str)
-
pre = str.split("\n").inject(str[/^[ \t]*/].split("")) do |pre, line|
-
line[/^[ \t]*/].split("").zip(pre).inject([]) do |arr, (a, b)|
-
break arr if a != b
-
arr << a
-
end
-
end.join
-
str.gsub(/^#{pre}/, '')
-
end
-
end
-
end
-
1
module Sass
-
1
module Tree
-
# A dynamic node representing a Sass `@debug` statement.
-
#
-
# @see Sass::Tree
-
1
class DebugNode < Node
-
# The expression to print.
-
# @return [Script::Node]
-
1
attr_accessor :expr
-
-
# @param expr [Script::Node] The expression to print
-
1
def initialize(expr)
-
@expr = expr
-
super()
-
end
-
end
-
end
-
end
-
1
module Sass::Tree
-
# A static node representing an unproccessed Sass `@`-directive.
-
# Directives known to Sass, like `@for` and `@debug`,
-
# are handled by their own nodes;
-
# only CSS directives like `@media` and `@font-face` become {DirectiveNode}s.
-
#
-
# `@import` and `@charset` are special cases;
-
# they become {ImportNode}s and {CharsetNode}s, respectively.
-
#
-
# @see Sass::Tree
-
1
class DirectiveNode < Node
-
# The text of the directive, `@` and all.
-
#
-
# @return [String]
-
1
attr_accessor :value
-
-
# @param value [String] See \{#value}
-
1
def initialize(value)
-
@value = value
-
super()
-
end
-
end
-
end
-
1
require 'sass/tree/node'
-
-
1
module Sass::Tree
-
# A dynamic node representing a Sass `@each` loop.
-
#
-
# @see Sass::Tree
-
1
class EachNode < Node
-
# The name of the loop variable.
-
# @return [String]
-
1
attr_reader :var
-
-
# The parse tree for the list.
-
# @param [Script::Node]
-
1
attr_accessor :list
-
-
# @param var [String] The name of the loop variable
-
# @param list [Script::Node] The parse tree for the list
-
1
def initialize(var, list)
-
@var = var
-
@list = list
-
super()
-
end
-
end
-
end
-
1
require 'sass/tree/node'
-
-
1
module Sass::Tree
-
# A static node reprenting an `@extend` directive.
-
#
-
# @see Sass::Tree
-
1
class ExtendNode < Node
-
# The parsed selector after interpolation has been resolved.
-
# Only set once {Tree::Visitors::Perform} has been run.
-
#
-
# @return [Selector::CommaSequence]
-
1
attr_accessor :resolved_selector
-
-
# The CSS selector to extend, interspersed with {Sass::Script::Node}s
-
# representing `#{}`-interpolation.
-
#
-
# @return [Array<String, Sass::Script::Node>]
-
1
attr_accessor :selector
-
-
# @param selector [Array<String, Sass::Script::Node>]
-
# The CSS selector to extend,
-
# interspersed with {Sass::Script::Node}s
-
# representing `#{}`-interpolation.
-
1
def initialize(selector)
-
@selector = selector
-
super()
-
end
-
end
-
end
-
1
require 'sass/tree/node'
-
-
1
module Sass::Tree
-
# A dynamic node representing a Sass `@for` loop.
-
#
-
# @see Sass::Tree
-
1
class ForNode < Node
-
# The name of the loop variable.
-
# @return [String]
-
1
attr_reader :var
-
-
# The parse tree for the initial expression.
-
# @return [Script::Node]
-
1
attr_accessor :from
-
-
# The parse tree for the final expression.
-
# @return [Script::Node]
-
1
attr_accessor :to
-
-
# Whether to include `to` in the loop or stop just before.
-
# @return [Boolean]
-
1
attr_reader :exclusive
-
-
# @param var [String] See \{#var}
-
# @param from [Script::Node] See \{#from}
-
# @param to [Script::Node] See \{#to}
-
# @param exclusive [Boolean] See \{#exclusive}
-
1
def initialize(var, from, to, exclusive)
-
@var = var
-
@from = from
-
@to = to
-
@exclusive = exclusive
-
super()
-
end
-
end
-
end
-
1
module Sass
-
1
module Tree
-
# A dynamic node representing a function definition.
-
#
-
# @see Sass::Tree
-
1
class FunctionNode < Node
-
# The name of the function.
-
# @return [String]
-
1
attr_reader :name
-
-
# The arguments to the function. Each element is a tuple
-
# containing the variable for argument and the parse tree for
-
# the default value of the argument
-
#
-
# @return [Array<Script::Node>]
-
1
attr_accessor :args
-
-
# @param name [String] The function name
-
# @param args [Array<(Script::Node, Script::Node)>] The arguments for the function.
-
1
def initialize(name, args)
-
@name = name
-
@args = args
-
super()
-
end
-
end
-
end
-
end
-
1
require 'sass/tree/node'
-
-
1
module Sass::Tree
-
# A dynamic node representing a Sass `@if` statement.
-
#
-
# {IfNode}s are a little odd, in that they also represent `@else` and `@else if`s.
-
# This is done as a linked list:
-
# each {IfNode} has a link (\{#else}) to the next {IfNode}.
-
#
-
# @see Sass::Tree
-
1
class IfNode < Node
-
# The conditional expression.
-
# If this is nil, this is an `@else` node, not an `@else if`.
-
#
-
# @return [Script::Expr]
-
1
attr_accessor :expr
-
-
# The next {IfNode} in the if-else list, or `nil`.
-
#
-
# @return [IfNode]
-
1
attr_accessor :else
-
-
# @param expr [Script::Expr] See \{#expr}
-
1
def initialize(expr)
-
@expr = expr
-
@last_else = self
-
super()
-
end
-
-
# Append an `@else` node to the end of the list.
-
#
-
# @param node [IfNode] The `@else` node to append
-
1
def add_else(node)
-
@last_else.else = node
-
@last_else = node
-
end
-
-
1
def _dump(f)
-
Marshal.dump([self.expr, self.else, self.children])
-
end
-
-
1
def self._load(data)
-
expr, else_, children = Marshal.load(data)
-
node = IfNode.new(expr)
-
node.else = else_
-
node.children = children
-
node.instance_variable_set('@last_else',
-
node.else ? node.else.instance_variable_get('@last_else') : node)
-
node
-
end
-
end
-
end
-
1
module Sass
-
1
module Tree
-
# A static node that wraps the {Sass::Tree} for an `@import`ed file.
-
# It doesn't have a functional purpose other than to add the `@import`ed file
-
# to the backtrace if an error occurs.
-
1
class ImportNode < RootNode
-
# The name of the imported file as it appears in the Sass document.
-
#
-
# @return [String]
-
1
attr_reader :imported_filename
-
-
# @param imported_filename [String] The name of the imported file
-
1
def initialize(imported_filename)
-
@imported_filename = imported_filename
-
super(nil)
-
end
-
-
1
def invisible?; to_s.empty?; end
-
-
# Returns the imported file.
-
#
-
# @return [Sass::Engine]
-
# @raise [Sass::SyntaxError] If no file could be found to import.
-
1
def imported_file
-
@imported_file ||= import
-
end
-
-
# Returns whether or not this import should emit a CSS @import declaration
-
#
-
# @return [Boolean] Whether or not this is a simple CSS @import declaration.
-
1
def css_import?
-
if @imported_filename =~ /\.css$/
-
@imported_filename
-
elsif imported_file.is_a?(String) && imported_file =~ /\.css$/
-
imported_file
-
end
-
end
-
-
1
private
-
-
1
def import
-
paths = @options[:load_paths]
-
-
if @options[:importer]
-
f = @options[:importer].find_relative(
-
@imported_filename, @options[:filename], @options.dup)
-
return f if f
-
end
-
-
paths.each do |p|
-
if f = p.find(@imported_filename, @options.dup)
-
return f
-
end
-
end
-
-
message = "File to import not found or unreadable: #{@imported_filename}.\n"
-
if paths.size == 1
-
message << "Load path: #{paths.first}"
-
else
-
message << "Load paths:\n " << paths.join("\n ")
-
end
-
raise SyntaxError.new(message)
-
rescue SyntaxError => e
-
raise SyntaxError.new(e.message, :line => self.line, :filename => @filename)
-
end
-
end
-
end
-
end
-
1
module Sass::Tree
-
# A static node representing a `@media` rule.
-
# `@media` rules behave differently from other directives
-
# in that when they're nested within rules,
-
# they bubble up to top-level.
-
#
-
# @see Sass::Tree
-
1
class MediaNode < DirectiveNode
-
# The media query. A list of comma-separated queries (e.g. `print` or `screen`).
-
#
-
# @return [Array<String>]
-
1
attr_accessor :query
-
-
# @see RuleNode#tabs
-
1
attr_accessor :tabs
-
-
# @see RuleNode#group_end
-
1
attr_accessor :group_end
-
-
# @param query [Array<String>] See \{#query}
-
1
def initialize(query)
-
@query = query
-
@tabs = 0
-
super('')
-
end
-
-
# @see DirectiveNode#value
-
1
def value
-
"@media #{query.join(', ')}"
-
end
-
end
-
end
-
1
module Sass
-
1
module Tree
-
# A dynamic node representing a mixin definition.
-
#
-
# @see Sass::Tree
-
1
class MixinDefNode < Node
-
# The mixin name.
-
# @return [String]
-
1
attr_reader :name
-
-
# The arguments for the mixin.
-
# Each element is a tuple containing the variable for argument
-
# and the parse tree for the default value of the argument.
-
#
-
# @return [Array<(Script::Node, Script::Node)>]
-
1
attr_accessor :args
-
-
# @param name [String] The mixin name
-
# @param args [Array<(Script::Node, Script::Node)>] See \{#args}
-
1
def initialize(name, args)
-
@name = name
-
@args = args
-
super()
-
end
-
end
-
end
-
end
-
1
require 'sass/tree/node'
-
-
1
module Sass::Tree
-
# A static node representing a mixin include.
-
# When in a static tree, the sole purpose is to wrap exceptions
-
# to add the mixin to the backtrace.
-
#
-
# @see Sass::Tree
-
1
class MixinNode < Node
-
# The name of the mixin.
-
# @return [String]
-
1
attr_reader :name
-
-
# The arguments to the mixin.
-
# @return [Array<Script::Node>]
-
1
attr_accessor :args
-
-
# A hash from keyword argument names to values.
-
# @return [{String => Script::Node}]
-
1
attr_accessor :keywords
-
-
# @param name [String] The name of the mixin
-
# @param args [Array<Script::Node>] See \{#args}
-
# @param keywords [{String => Script::Node}] See \{#keywords}
-
1
def initialize(name, args, keywords)
-
@name = name
-
@args = args
-
@keywords = keywords
-
super()
-
end
-
end
-
end
-
1
module Sass
-
# A namespace for nodes in the Sass parse tree.
-
#
-
# The Sass parse tree has three states: dynamic, static Sass, and static CSS.
-
#
-
# When it's first parsed, a Sass document is in the dynamic state.
-
# It has nodes for mixin definitions and `@for` loops and so forth,
-
# in addition to nodes for CSS rules and properties.
-
# Nodes that only appear in this state are called **dynamic nodes**.
-
#
-
# {Tree::Visitors::Perform} creates a static Sass tree, which is different.
-
# It still has nodes for CSS rules and properties
-
# but it doesn't have any dynamic-generation-related nodes.
-
# The nodes in this state are in the same structure as the Sass document:
-
# rules and properties are nested beneath one another.
-
# Nodes that can be in this state or in the dynamic state
-
# are called **static nodes**.
-
#
-
# {Tree::Visitors::Cssize} is then used to create a static CSS tree.
-
# This is like a static Sass tree,
-
# but the structure exactly mirrors that of the generated CSS.
-
# Rules and properties can't be nested beneath one another in this state.
-
#
-
# Finally, {Tree::Visitors::ToCss} can be called on a static CSS tree
-
# to get the actual CSS code as a string.
-
1
module Tree
-
# The abstract superclass of all parse-tree nodes.
-
1
class Node
-
1
include Enumerable
-
-
# The child nodes of this node.
-
#
-
# @return [Array<Tree::Node>]
-
1
attr_accessor :children
-
-
# Whether or not this node has child nodes.
-
# This may be true even when \{#children} is empty,
-
# in which case this node has an empty block (e.g. `{}`).
-
#
-
# @return [Boolean]
-
1
attr_accessor :has_children
-
-
# The line of the document on which this node appeared.
-
#
-
# @return [Fixnum]
-
1
attr_accessor :line
-
-
# The name of the document on which this node appeared.
-
#
-
# @return [String]
-
1
attr_writer :filename
-
-
# The options hash for the node.
-
# See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
-
#
-
# @return [{Symbol => Object}]
-
1
attr_reader :options
-
-
1
def initialize
-
@children = []
-
end
-
-
# Sets the options hash for the node and all its children.
-
#
-
# @param options [{Symbol => Object}] The options
-
# @see #options
-
1
def options=(options)
-
Sass::Tree::Visitors::SetOptions.visit(self, options)
-
end
-
-
# @private
-
1
def children=(children)
-
self.has_children ||= !children.empty?
-
@children = children
-
end
-
-
# The name of the document on which this node appeared.
-
#
-
# @return [String]
-
1
def filename
-
@filename || (@options && @options[:filename])
-
end
-
-
# Appends a child to the node.
-
#
-
# @param child [Tree::Node, Array<Tree::Node>] The child node or nodes
-
# @raise [Sass::SyntaxError] if `child` is invalid
-
1
def <<(child)
-
return if child.nil?
-
if child.is_a?(Array)
-
child.each {|c| self << c}
-
else
-
self.has_children = true
-
@children << child
-
end
-
end
-
-
# Compares this node and another object (only other {Tree::Node}s will be equal).
-
# This does a structural comparison;
-
# if the contents of the nodes and all the child nodes are equivalent,
-
# then the nodes are as well.
-
#
-
# Only static nodes need to override this.
-
#
-
# @param other [Object] The object to compare with
-
# @return [Boolean] Whether or not this node and the other object
-
# are the same
-
# @see Sass::Tree
-
1
def ==(other)
-
self.class == other.class && other.children == children
-
end
-
-
# True if \{#to\_s} will return `nil`;
-
# that is, if the node shouldn't be rendered.
-
# Should only be called in a static tree.
-
#
-
# @return [Boolean]
-
1
def invisible?; false; end
-
-
# The output style. See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
-
#
-
# @return [Symbol]
-
1
def style
-
@options[:style]
-
end
-
-
# Computes the CSS corresponding to this static CSS tree.
-
#
-
# @return [String, nil] The resulting CSS
-
# @see Sass::Tree
-
1
def to_s
-
Sass::Tree::Visitors::ToCss.visit(self)
-
end
-
-
# Converts a static CSS tree (e.g. the output of \{Tree::Visitors::Cssize})
-
# into another static CSS tree,
-
# with the given extensions applied to all relevant {RuleNode}s.
-
#
-
# @todo Link this to the reference documentation on `@extend`
-
# when such a thing exists.
-
#
-
# @param extends [Sass::Util::SubsetMap{Selector::Simple => Selector::Sequence}]
-
# The extensions to perform on this tree
-
# @return [Tree::Node] The resulting tree of static CSS nodes.
-
# @raise [Sass::SyntaxError] Only if there's a programmer error
-
# and this is not a static CSS tree
-
1
def do_extend(extends)
-
node = dup
-
node.children = children.map {|c| c.do_extend(extends)}
-
node
-
rescue Sass::SyntaxError => e
-
e.modify_backtrace(:filename => filename, :line => line)
-
raise e
-
end
-
-
# Iterates through each node in the tree rooted at this node
-
# in a pre-order walk.
-
#
-
# @yield node
-
# @yieldparam node [Node] a node in the tree
-
1
def each
-
yield self
-
children.each {|c| c.each {|n| yield n}}
-
end
-
-
# Converts a node to Sass code that will generate it.
-
#
-
# @param options [{Symbol => Object}] An options hash (see {Sass::CSS#initialize})
-
# @return [String] The Sass code corresponding to the node
-
1
def to_sass(options = {})
-
Sass::Tree::Visitors::Convert.visit(self, options, :sass)
-
end
-
-
# Converts a node to SCSS code that will generate it.
-
#
-
# @param options [{Symbol => Object}] An options hash (see {Sass::CSS#initialize})
-
# @return [String] The Sass code corresponding to the node
-
1
def to_scss(options = {})
-
Sass::Tree::Visitors::Convert.visit(self, options, :scss)
-
end
-
-
# Return a deep clone of this node.
-
# The child nodes are cloned, but options are not.
-
#
-
# @return [Node]
-
1
def deep_copy
-
Sass::Tree::Visitors::DeepCopy.visit(self)
-
end
-
-
1
protected
-
-
# @see Sass::Shared.balance
-
# @raise [Sass::SyntaxError] if the brackets aren't balanced
-
1
def balance(*args)
-
res = Sass::Shared.balance(*args)
-
return res if res
-
raise Sass::SyntaxError.new("Unbalanced brackets.", :line => line)
-
end
-
end
-
end
-
end
-
1
module Sass::Tree
-
# A static node reprenting a CSS property.
-
#
-
# @see Sass::Tree
-
1
class PropNode < Node
-
# The name of the property,
-
# interspersed with {Sass::Script::Node}s
-
# representing `#{}`-interpolation.
-
# Any adjacent strings will be merged together.
-
#
-
# @return [Array<String, Sass::Script::Node>]
-
1
attr_accessor :name
-
-
# The name of the property
-
# after any interpolated SassScript has been resolved.
-
# Only set once \{Tree::Visitors::Perform} has been run.
-
#
-
# @return [String]
-
1
attr_accessor :resolved_name
-
-
# The value of the property.
-
#
-
# @return [Sass::Script::Node]
-
1
attr_accessor :value
-
-
# The value of the property
-
# after any interpolated SassScript has been resolved.
-
# Only set once \{Tree::Visitors::Perform} has been run.
-
#
-
# @return [String]
-
1
attr_accessor :resolved_value
-
-
# How deep this property is indented
-
# relative to a normal property.
-
# This is only greater than 0 in the case that:
-
#
-
# * This node is in a CSS tree
-
# * The style is :nested
-
# * This is a child property of another property
-
# * The parent property has a value, and thus will be rendered
-
#
-
# @return [Fixnum]
-
1
attr_accessor :tabs
-
-
# @param name [Array<String, Sass::Script::Node>] See \{#name}
-
# @param value [Sass::Script::Node] See \{#value}
-
# @param prop_syntax [Symbol] `:new` if this property uses `a: b`-style syntax,
-
# `:old` if it uses `:a b`-style syntax
-
1
def initialize(name, value, prop_syntax)
-
@name = Sass::Util.strip_string_array(
-
Sass::Util.merge_adjacent_strings(name))
-
@value = value
-
@tabs = 0
-
@prop_syntax = prop_syntax
-
super()
-
end
-
-
# Compares the names and values of two properties.
-
#
-
# @param other [Object] The object to compare with
-
# @return [Boolean] Whether or not this node and the other object
-
# are the same
-
1
def ==(other)
-
self.class == other.class && name == other.name && value == other.value && super
-
end
-
-
# Returns a appropriate message indicating how to escape pseudo-class selectors.
-
# This only applies for old-style properties with no value,
-
# so returns the empty string if this is new-style.
-
#
-
# @return [String] The message
-
1
def pseudo_class_selector_message
-
return "" if @prop_syntax == :new || !value.is_a?(Sass::Script::String) || !value.value.empty?
-
"\nIf #{declaration.dump} should be a selector, use \"\\#{declaration}\" instead."
-
end
-
-
# Computes the Sass or SCSS code for the variable declaration.
-
# This is like \{#to\_scss} or \{#to\_sass},
-
# except it doesn't print any child properties or a trailing semicolon.
-
#
-
# @param opts [{Symbol => Object}] The options hash for the tree.
-
# @param fmt [Symbol] `:scss` or `:sass`.
-
1
def declaration(opts = {:old => @prop_syntax == :old}, fmt = :sass)
-
name = self.name.map {|n| n.is_a?(String) ? n : "\#{#{n.to_sass(opts)}}"}.join
-
if name[0] == ?:
-
raise Sass::SyntaxError.new("The \"#{name}: #{self.class.val_to_sass(value, opts)}\" hack is not allowed in the Sass indented syntax")
-
end
-
-
old = opts[:old] && fmt == :sass
-
initial = old ? ':' : ''
-
mid = old ? '' : ':'
-
"#{initial}#{name}#{mid} #{self.class.val_to_sass(value, opts)}".rstrip
-
end
-
-
1
private
-
-
1
def check!
-
if @options[:property_syntax] && @options[:property_syntax] != @prop_syntax
-
raise Sass::SyntaxError.new(
-
"Illegal property syntax: can't use #{@prop_syntax} syntax when :property_syntax => #{@options[:property_syntax].inspect} is set.")
-
elsif resolved_value.empty?
-
raise Sass::SyntaxError.new("Invalid property: #{declaration.dump} (no value)." +
-
pseudo_class_selector_message)
-
end
-
end
-
-
1
class << self
-
# @private
-
1
def val_to_sass(value, opts)
-
val_to_sass_comma(value, opts).to_sass(opts)
-
end
-
-
1
private
-
-
1
def val_to_sass_comma(node, opts)
-
return node unless node.is_a?(Sass::Script::Operation)
-
return val_to_sass_concat(node, opts) unless node.operator == :comma
-
-
Sass::Script::Operation.new(
-
val_to_sass_concat(node.operand1, opts),
-
val_to_sass_comma(node.operand2, opts),
-
node.operator)
-
end
-
-
1
def val_to_sass_concat(node, opts)
-
return node unless node.is_a?(Sass::Script::Operation)
-
return val_to_sass_div(node, opts) unless node.operator == :space
-
-
Sass::Script::Operation.new(
-
val_to_sass_div(node.operand1, opts),
-
val_to_sass_concat(node.operand2, opts),
-
node.operator)
-
end
-
-
1
def val_to_sass_div(node, opts)
-
unless node.is_a?(Sass::Script::Operation) && node.operator == :div &&
-
node.operand1.is_a?(Sass::Script::Number) &&
-
node.operand2.is_a?(Sass::Script::Number) &&
-
(!node.operand1.original || !node.operand2.original)
-
return node
-
end
-
-
Sass::Script::String.new("(#{node.to_sass(opts)})")
-
end
-
-
end
-
end
-
end
-
1
module Sass
-
1
module Tree
-
# A dynamic node representing returning from a function.
-
#
-
# @see Sass::Tree
-
1
class ReturnNode < Node
-
# The expression to return.
-
# @type [Script::Node]
-
1
attr_accessor :expr
-
-
# @param expr [Script::Node] The expression to return
-
1
def initialize(expr)
-
@expr = expr
-
super()
-
end
-
end
-
end
-
end
-
1
module Sass
-
1
module Tree
-
# A static node that is the root node of the Sass document.
-
1
class RootNode < Node
-
# The Sass template from which this node was created
-
#
-
# @param template [String]
-
1
attr_reader :template
-
-
# @param template [String] The Sass template from which this node was created
-
1
def initialize(template)
-
super()
-
@template = template
-
end
-
-
# Runs the dynamic Sass code *and* computes the CSS for the tree.
-
# @see #to_s
-
1
def render
-
Visitors::CheckNesting.visit(self)
-
result = Visitors::Perform.visit(self)
-
Visitors::CheckNesting.visit(result) # Check again to validate mixins
-
result, extends = Visitors::Cssize.visit(result)
-
result = result.do_extend(extends) unless extends.empty?
-
result.to_s
-
end
-
end
-
end
-
end
-
1
require 'pathname'
-
1
require 'uri'
-
-
1
module Sass::Tree
-
# A static node reprenting a CSS rule.
-
#
-
# @see Sass::Tree
-
1
class RuleNode < Node
-
# The character used to include the parent selector
-
1
PARENT = '&'
-
-
# The CSS selector for this rule,
-
# interspersed with {Sass::Script::Node}s
-
# representing `#{}`-interpolation.
-
# Any adjacent strings will be merged together.
-
#
-
# @return [Array<String, Sass::Script::Node>]
-
1
attr_accessor :rule
-
-
# The CSS selector for this rule,
-
# without any unresolved interpolation
-
# but with parent references still intact.
-
# It's only set once {Tree::Node#perform} has been called.
-
#
-
# @return [Selector::CommaSequence]
-
1
attr_accessor :parsed_rules
-
-
# The CSS selector for this rule,
-
# without any unresolved interpolation or parent references.
-
# It's only set once {Tree::Visitors::Cssize} has been run.
-
#
-
# @return [Selector::CommaSequence]
-
1
attr_accessor :resolved_rules
-
-
# How deep this rule is indented
-
# relative to a base-level rule.
-
# This is only greater than 0 in the case that:
-
#
-
# * This node is in a CSS tree
-
# * The style is :nested
-
# * This is a child rule of another rule
-
# * The parent rule has properties, and thus will be rendered
-
#
-
# @return [Fixnum]
-
1
attr_accessor :tabs
-
-
# Whether or not this rule is the last rule in a nested group.
-
# This is only set in a CSS tree.
-
#
-
# @return [Boolean]
-
1
attr_accessor :group_end
-
-
# The stack trace.
-
# This is only readable in a CSS tree as it is written during the perform step
-
# and only when the :trace_selectors option is set.
-
#
-
# @return [Array<String>]
-
1
attr_accessor :stack_trace
-
-
# @param rule [Array<String, Sass::Script::Node>]
-
# The CSS rule. See \{#rule}
-
1
def initialize(rule)
-
merged = Sass::Util.merge_adjacent_strings(rule)
-
@rule = Sass::Util.strip_string_array(merged)
-
@tabs = 0
-
try_to_parse_non_interpolated_rules
-
super()
-
end
-
-
# If we've precached the parsed selector, set the line on it, too.
-
1
def line=(line)
-
@parsed_rules.line = line if @parsed_rules
-
super
-
end
-
-
# If we've precached the parsed selector, set the filename on it, too.
-
1
def filename=(filename)
-
@parsed_rules.filename = filename if @parsed_rules
-
super
-
end
-
-
# Compares the contents of two rules.
-
#
-
# @param other [Object] The object to compare with
-
# @return [Boolean] Whether or not this node and the other object
-
# are the same
-
1
def ==(other)
-
self.class == other.class && rule == other.rule && super
-
end
-
-
# Adds another {RuleNode}'s rules to this one's.
-
#
-
# @param node [RuleNode] The other node
-
1
def add_rules(node)
-
@rule = Sass::Util.strip_string_array(
-
Sass::Util.merge_adjacent_strings(@rule + ["\n"] + node.rule))
-
try_to_parse_non_interpolated_rules
-
end
-
-
# @return [Boolean] Whether or not this rule is continued on the next line
-
1
def continued?
-
last = @rule.last
-
last.is_a?(String) && last[-1] == ?,
-
end
-
-
# Extends this Rule's selector with the given `extends`.
-
#
-
# @see Node#do_extend
-
1
def do_extend(extends)
-
node = dup
-
node.resolved_rules = resolved_rules.do_extend(extends)
-
node
-
end
-
-
# A hash that will be associated with this rule in the CSS document
-
# if the {file:SASS_REFERENCE.md#debug_info-option `:debug_info` option} is enabled.
-
# This data is used by e.g. [the FireSass Firebug extension](https://addons.mozilla.org/en-US/firefox/addon/103988).
-
#
-
# @return [{#to_s => #to_s}]
-
1
def debug_info
-
{:filename => filename && ("file://" + URI.escape(File.expand_path(filename))),
-
:line => self.line}
-
end
-
-
1
private
-
-
1
def try_to_parse_non_interpolated_rules
-
if @rule.all? {|t| t.kind_of?(String)}
-
# We don't use real filename/line info because we don't have it yet.
-
# When we get it, we'll set it on the parsed rules if possible.
-
parser = Sass::SCSS::StaticParser.new(@rule.join.strip, '', 1)
-
@parsed_rules = parser.parse_selector rescue nil
-
end
-
end
-
end
-
end
-
1
module Sass
-
1
module Tree
-
# A dynamic node representing a variable definition.
-
#
-
# @see Sass::Tree
-
1
class VariableNode < Node
-
# The name of the variable.
-
# @return [String]
-
1
attr_reader :name
-
-
# The parse tree for the variable value.
-
# @return [Script::Node]
-
1
attr_accessor :expr
-
-
# Whether this is a guarded variable assignment (`!default`).
-
# @return [Boolean]
-
1
attr_reader :guarded
-
-
# @param name [String] The name of the variable
-
# @param expr [Script::Node] See \{#expr}
-
# @param guarded [Boolean] See \{#guarded}
-
1
def initialize(name, expr, guarded)
-
@name = name
-
@expr = expr
-
@guarded = guarded
-
super()
-
end
-
end
-
end
-
end
-
# Visitors are used to traverse the Sass parse tree.
-
# Visitors should extend {Visitors::Base},
-
# which provides a small amount of scaffolding for traversal.
-
1
module Sass::Tree::Visitors
-
# The abstract base class for Sass visitors.
-
# Visitors should extend this class,
-
# then implement `visit_*` methods for each node they care about
-
# (e.g. `visit_rule` for {RuleNode} or `visit_for` for {ForNode}).
-
# These methods take the node in question as argument.
-
# They may `yield` to visit the child nodes of the current node.
-
#
-
# *Note*: due to the unusual nature of {Sass::Tree::IfNode},
-
# special care must be taken to ensure that it is properly handled.
-
# In particular, there is no built-in scaffolding
-
# for dealing with the return value of `@else` nodes.
-
#
-
# @abstract
-
1
class Base
-
# Runs the visitor on a tree.
-
#
-
# @param root [Tree::Node] The root node of the Sass tree.
-
# @return [Object] The return value of \{#visit} for the root node.
-
1
def self.visit(root)
-
new.send(:visit, root)
-
end
-
-
1
protected
-
-
# Runs the visitor on the given node.
-
# This can be overridden by subclasses that need to do something for each node.
-
#
-
# @param node [Tree::Node] The node to visit.
-
# @return [Object] The return value of the `visit_*` method for this node.
-
1
def visit(node)
-
method = "visit_#{node_name node}"
-
if self.respond_to?(method)
-
self.send(method, node) {visit_children(node)}
-
else
-
visit_children(node)
-
end
-
end
-
-
# Visit the child nodes for a given node.
-
# This can be overridden by subclasses that need to do something
-
# with the child nodes' return values.
-
#
-
# This method is run when `visit_*` methods `yield`,
-
# and its return value is returned from the `yield`.
-
#
-
# @param parent [Tree::Node] The parent node of the children to visit.
-
# @return [Array<Object>] The return values of the `visit_*` methods for the children.
-
1
def visit_children(parent)
-
parent.children.map {|c| visit(c)}
-
end
-
-
1
NODE_NAME_RE = /.*::(.*?)Node$/
-
-
# Returns the name of a node as used in the `visit_*` method.
-
#
-
# @param [Tree::Node] node The node.
-
# @return [String] The name.
-
1
def node_name(node)
-
@@node_names ||= {}
-
@@node_names[node.class.name] ||= node.class.name.gsub(NODE_NAME_RE, '\\1').downcase
-
end
-
-
# `yield`s, then runs the visitor on the `@else` clause if the node has one.
-
# This exists to ensure that the contents of the `@else` clause get visited.
-
1
def visit_if(node)
-
yield
-
visit(node.else) if node.else
-
node
-
end
-
end
-
end
-
# A visitor for checking that all nodes are properly nested.
-
1
class Sass::Tree::Visitors::CheckNesting < Sass::Tree::Visitors::Base
-
1
protected
-
-
1
def visit(node)
-
if error = (@parent && (
-
try_send("invalid_#{node_name @parent}_child?", @parent, node) ||
-
try_send("invalid_#{node_name node}_parent?", @parent, node))) ||
-
(@real_parent && (
-
try_send("invalid_#{node_name @real_parent}_real_child?", @real_parent, node) ||
-
try_send("invalid_#{node_name node}_real_parent?", @real_parent, node)))
-
raise Sass::SyntaxError.new(error)
-
end
-
super
-
rescue Sass::SyntaxError => e
-
e.modify_backtrace(:filename => node.filename, :line => node.line)
-
raise e
-
end
-
-
1
CONTROL_NODES = [Sass::Tree::EachNode, Sass::Tree::ForNode, Sass::Tree::IfNode, Sass::Tree::WhileNode]
-
1
SCRIPT_NODES = [Sass::Tree::ImportNode, Sass::Tree::MixinNode] + CONTROL_NODES
-
1
def visit_children(parent)
-
old_parent = @parent
-
@parent = parent unless is_any_of?(parent, SCRIPT_NODES)
-
old_real_parent, @real_parent = @real_parent, parent
-
super
-
ensure
-
@parent = old_parent
-
@real_parent = old_real_parent
-
end
-
-
1
def visit_root(node)
-
yield
-
rescue Sass::SyntaxError => e
-
e.sass_template ||= node.template
-
raise e
-
end
-
-
1
def visit_import(node)
-
yield
-
rescue Sass::SyntaxError => e
-
e.modify_backtrace(:filename => node.children.first.filename)
-
e.add_backtrace(:filename => node.filename, :line => node.line)
-
raise e
-
end
-
-
1
def invalid_charset_parent?(parent, child)
-
"@charset may only be used at the root of a document." unless parent.is_a?(Sass::Tree::RootNode)
-
end
-
-
1
VALID_EXTEND_PARENTS = [Sass::Tree::RuleNode, Sass::Tree::MixinDefNode]
-
1
def invalid_extend_parent?(parent, child)
-
unless is_any_of?(parent, VALID_EXTEND_PARENTS)
-
"Extend directives may only be used within rules."
-
end
-
end
-
-
1
def invalid_function_parent?(parent, child)
-
"Functions may only be defined at the root of a document." unless parent.is_a?(Sass::Tree::RootNode)
-
end
-
-
1
VALID_FUNCTION_CHILDREN = [
-
Sass::Tree::CommentNode, Sass::Tree::DebugNode, Sass::Tree::ReturnNode,
-
Sass::Tree::VariableNode, Sass::Tree::WarnNode
-
] + CONTROL_NODES
-
1
def invalid_function_child?(parent, child)
-
unless is_any_of?(child, VALID_FUNCTION_CHILDREN)
-
"Functions can only contain variable declarations and control directives."
-
end
-
end
-
-
1
VALID_IMPORT_PARENTS = [
-
Sass::Tree::IfNode, Sass::Tree::ForNode, Sass::Tree::WhileNode,
-
Sass::Tree::EachNode, Sass::Tree::MixinDefNode
-
]
-
1
def invalid_import_parent?(parent, child)
-
if is_any_of?(@real_parent, VALID_IMPORT_PARENTS)
-
return "Import directives may not be used within control directives or mixins."
-
end
-
return if parent.is_a?(Sass::Tree::RootNode)
-
return "CSS import directives may only be used at the root of a document." if child.css_import?
-
# If this is a nested @import, we need to make sure it doesn't have anything
-
# that's legal at top-level but not in the current context (e.g. mixin defs).
-
child.imported_file.to_tree.children.each {|c| visit(c)}
-
nil
-
rescue Sass::SyntaxError => e
-
e.modify_backtrace(:filename => child.imported_file.options[:filename])
-
e.add_backtrace(:filename => child.filename, :line => child.line)
-
raise e
-
end
-
-
1
def invalid_import_real_parent?(parent, child)
-
-
end
-
-
1
def invalid_mixindef_parent?(parent, child)
-
"Mixins may only be defined at the root of a document." unless parent.is_a?(Sass::Tree::RootNode)
-
end
-
-
1
VALID_PROP_CHILDREN = [Sass::Tree::CommentNode, Sass::Tree::PropNode, Sass::Tree::MixinNode] + CONTROL_NODES
-
1
def invalid_prop_child?(parent, child)
-
unless is_any_of?(child, VALID_PROP_CHILDREN)
-
"Illegal nesting: Only properties may be nested beneath properties."
-
end
-
end
-
-
1
VALID_PROP_PARENTS = [Sass::Tree::RuleNode, Sass::Tree::PropNode,
-
Sass::Tree::MixinDefNode, Sass::Tree::DirectiveNode]
-
1
def invalid_prop_parent?(parent, child)
-
unless is_any_of?(parent, VALID_PROP_PARENTS)
-
"Properties are only allowed within rules, directives, or other properties." + child.pseudo_class_selector_message
-
end
-
end
-
-
1
def invalid_return_parent?(parent, child)
-
"@return may only be used within a function." unless parent.is_a?(Sass::Tree::FunctionNode)
-
end
-
-
1
private
-
-
1
def is_any_of?(val, classes)
-
for c in classes
-
return true if val.is_a?(c)
-
end
-
return false
-
end
-
-
1
def try_send(method, *args)
-
return unless respond_to?(method)
-
send(method, *args)
-
end
-
end
-
-
# A visitor for converting a Sass tree into a source string.
-
1
class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
-
# Runs the visitor on a tree.
-
#
-
# @param root [Tree::Node] The root node of the Sass tree.
-
# @param options [{Symbol => Object}] An options hash (see {Sass::CSS#initialize}).
-
# @param format [Symbol] `:sass` or `:scss`.
-
# @return [String] The Sass or SCSS source for the tree.
-
1
def self.visit(root, options, format)
-
new(options, format).send(:visit, root)
-
end
-
-
1
protected
-
-
1
def initialize(options, format)
-
@options = options
-
@format = format
-
@tabs = 0
-
end
-
-
1
def visit_children(parent)
-
@tabs += 1
-
return @format == :sass ? "\n" : " {}\n" if parent.children.empty?
-
(@format == :sass ? "\n" : " {\n") + super.join.rstrip + (@format == :sass ? "\n" : " }\n")
-
ensure
-
@tabs -= 1
-
end
-
-
# Ensures proper spacing between top-level nodes.
-
1
def visit_root(node)
-
Sass::Util.enum_cons(node.children + [nil], 2).map do |child, nxt|
-
visit(child) +
-
if nxt &&
-
(child.is_a?(Sass::Tree::CommentNode) &&
-
child.line + child.lines + 1 == nxt.line) ||
-
(child.is_a?(Sass::Tree::ImportNode) && nxt.is_a?(Sass::Tree::ImportNode) &&
-
child.line + 1 == nxt.line) ||
-
(child.is_a?(Sass::Tree::VariableNode) && nxt.is_a?(Sass::Tree::VariableNode) &&
-
child.line + 1 == nxt.line)
-
""
-
else
-
"\n"
-
end
-
end.join.rstrip + "\n"
-
end
-
-
1
def visit_charset(node)
-
"#{tab_str}@charset \"#{node.name}\"#{semi}\n"
-
end
-
-
1
def visit_comment(node)
-
value = node.value.map do |r|
-
next r if r.is_a?(String)
-
"\#{#{r.to_sass(@options)}}"
-
end.join
-
-
content = if @format == :sass
-
content = value.gsub(/\*\/$/, '').rstrip
-
if content =~ /\A[ \t]/
-
# Re-indent SCSS comments like this:
-
# /* foo
-
# bar
-
# baz */
-
content.gsub!(/^/, ' ')
-
content.sub!(/\A([ \t]*)\/\*/, '/*\1')
-
end
-
-
content =
-
unless content.include?("\n")
-
content
-
else
-
content.gsub!(/\n( \*|\/\/)/, "\n ")
-
spaces = content.scan(/\n( *)/).map {|s| s.first.size}.min
-
sep = node.silent ? "\n//" : "\n *"
-
if spaces >= 2
-
content.gsub(/\n /, sep)
-
else
-
content.gsub(/\n#{' ' * spaces}/, sep)
-
end
-
end
-
-
content.gsub!(/\A\/\*/, '//') if node.silent
-
content.gsub!(/^/, tab_str)
-
content.rstrip + "\n"
-
else
-
spaces = (' ' * [@tabs - value[/^ */].size, 0].max)
-
content = if node.silent
-
value.gsub(/^[\/ ]\*/, '//').gsub(/ *\*\/$/, '')
-
else
-
value
-
end.gsub(/^/, spaces) + "\n"
-
content
-
end
-
if node.loud
-
if node.silent
-
content.gsub!(%r{^\s*(//!?)}, '//!')
-
else
-
content.sub!(%r{^\s*(/\*)}, '/*!')
-
end
-
end
-
content
-
end
-
-
1
def visit_debug(node)
-
"#{tab_str}@debug #{node.expr.to_sass(@options)}#{semi}\n"
-
end
-
-
1
def visit_directive(node)
-
res = "#{tab_str}#{node.value}"
-
return res + "#{semi}\n" unless node.has_children
-
res + yield + "\n"
-
end
-
-
1
def visit_each(node)
-
"#{tab_str}@each $#{dasherize(node.var)} in #{node.list.to_sass(@options)}#{yield}"
-
end
-
-
1
def visit_extend(node)
-
"#{tab_str}@extend #{selector_to_src(node.selector).lstrip}#{semi}\n"
-
end
-
-
1
def visit_for(node)
-
"#{tab_str}@for $#{dasherize(node.var)} from #{node.from.to_sass(@options)} " +
-
"#{node.exclusive ? "to" : "through"} #{node.to.to_sass(@options)}#{yield}"
-
end
-
-
1
def visit_function(node)
-
args = node.args.map do |v, d|
-
d ? "#{v.to_sass(@options)}: #{d.to_sass(@options)}" : v.to_sass(@options)
-
end.join(", ")
-
-
"#{tab_str}@function #{dasherize(node.name)}(#{args})#{yield}"
-
end
-
-
1
def visit_if(node)
-
name =
-
if !@is_else; "if"
-
elsif node.expr; "else if"
-
else; "else"
-
end
-
str = "#{tab_str}@#{name}"
-
str << " #{node.expr.to_sass(@options)}" if node.expr
-
str << yield
-
@is_else = true
-
str << visit(node.else) if node.else
-
str
-
ensure
-
@is_else = false
-
end
-
-
1
def visit_import(node)
-
quote = @format == :scss ? '"' : ''
-
"#{tab_str}@import #{quote}#{node.imported_filename}#{quote}#{semi}\n"
-
end
-
-
1
def visit_media(node)
-
"#{tab_str}@media #{node.query.join(', ')}#{yield}"
-
end
-
-
1
def visit_mixindef(node)
-
args =
-
if node.args.empty?
-
""
-
else
-
'(' + node.args.map do |v, d|
-
if d
-
"#{v.to_sass(@options)}: #{d.to_sass(@options)}"
-
else
-
v.to_sass(@options)
-
end
-
end.join(", ") + ')'
-
end
-
-
"#{tab_str}#{@format == :sass ? '=' : '@mixin '}#{dasherize(node.name)}#{args}#{yield}"
-
end
-
-
1
def visit_mixin(node)
-
unless node.args.empty? && node.keywords.empty?
-
args = node.args.map {|a| a.to_sass(@options)}.join(", ")
-
keywords = Sass::Util.hash_to_a(node.keywords).
-
map {|k, v| "$#{dasherize(k)}: #{v.to_sass(@options)}"}.join(', ')
-
arglist = "(#{args}#{', ' unless args.empty? || keywords.empty?}#{keywords})"
-
end
-
"#{tab_str}#{@format == :sass ? '+' : '@include '}#{dasherize(node.name)}#{arglist}#{semi}\n"
-
end
-
-
1
def visit_prop(node)
-
res = tab_str + node.declaration(@options, @format)
-
return res + semi + "\n" if node.children.empty?
-
res + yield.rstrip + semi + "\n"
-
end
-
-
1
def visit_return(node)
-
"#{tab_str}@return #{node.expr.to_sass(@options)}#{semi}\n"
-
end
-
-
1
def visit_rule(node)
-
if @format == :sass
-
name = selector_to_sass(node.rule)
-
name = "\\" + name if name[0] == ?:
-
name.gsub(/^/, tab_str) + yield
-
elsif @format == :scss
-
name = selector_to_scss(node.rule)
-
res = name + yield
-
if node.children.last.is_a?(Sass::Tree::CommentNode) && node.children.last.silent
-
res.slice!(-3..-1)
-
res << "\n" << tab_str << "}\n"
-
end
-
res
-
end
-
end
-
-
1
def visit_variable(node)
-
"#{tab_str}$#{dasherize(node.name)}: #{node.expr.to_sass(@options)}#{' !default' if node.guarded}#{semi}\n"
-
end
-
-
1
def visit_warn(node)
-
"#{tab_str}@warn #{node.expr.to_sass(@options)}#{semi}\n"
-
end
-
-
1
def visit_while(node)
-
"#{tab_str}@while #{node.expr.to_sass(@options)}#{yield}"
-
end
-
-
1
private
-
-
1
def selector_to_src(sel)
-
@format == :sass ? selector_to_sass(sel) : selector_to_scss(sel)
-
end
-
-
1
def selector_to_sass(sel)
-
sel.map do |r|
-
if r.is_a?(String)
-
r.gsub(/(,)?([ \t]*)\n\s*/) {$1 ? "#{$1}#{$2}\n" : " "}
-
else
-
"\#{#{r.to_sass(@options)}}"
-
end
-
end.join
-
end
-
-
1
def selector_to_scss(sel)
-
sel.map {|r| r.is_a?(String) ? r : "\#{#{r.to_sass(@options)}}"}.
-
join.gsub(/^[ \t]*/, tab_str).gsub(/[ \t]*$/, '')
-
end
-
-
1
def semi
-
@format == :sass ? "" : ";"
-
end
-
-
1
def tab_str
-
' ' * @tabs
-
end
-
-
1
def dasherize(s)
-
if @options[:dasherize]
-
s.gsub('_', '-')
-
else
-
s
-
end
-
end
-
end
-
# A visitor for converting a static Sass tree into a static CSS tree.
-
1
class Sass::Tree::Visitors::Cssize < Sass::Tree::Visitors::Base
-
# @param root [Tree::Node] The root node of the tree to visit.
-
# @return [(Tree::Node, Sass::Util::SubsetMap)] The resulting tree of static nodes
-
# *and* the extensions defined for this tree
-
1
def self.visit(root); super; end
-
-
1
protected
-
-
# Returns the immediate parent of the current node.
-
# @return [Tree::Node]
-
1
attr_reader :parent
-
-
1
def initialize
-
@extends = Sass::Util::SubsetMap.new
-
end
-
-
# If an exception is raised, this adds proper metadata to the backtrace.
-
1
def visit(node)
-
super(node)
-
rescue Sass::SyntaxError => e
-
e.modify_backtrace(:filename => node.filename, :line => node.line)
-
raise e
-
end
-
-
# Keeps track of the current parent node.
-
1
def visit_children(parent)
-
with_parent parent do
-
parent.children = super.flatten
-
parent
-
end
-
end
-
-
# Runs a block of code with the current parent node
-
# replaced with the given node.
-
#
-
# @param parent [Tree::Node] The new parent for the duration of the block.
-
# @yield A block in which the parent is set to `parent`.
-
# @return [Object] The return value of the block.
-
1
def with_parent(parent)
-
old_parent, @parent = @parent, parent
-
yield
-
ensure
-
@parent = old_parent
-
end
-
-
# In Ruby 1.8, ensures that there's only one `@charset` directive
-
# and that it's at the top of the document.
-
#
-
# @return [(Tree::Node, Sass::Util::SubsetMap)] The resulting tree of static nodes
-
# *and* the extensions defined for this tree
-
1
def visit_root(node)
-
yield
-
-
if parent.nil?
-
# In Ruby 1.9 we can make all @charset nodes invisible
-
# and infer the final @charset from the encoding of the final string.
-
if Sass::Util.ruby1_8?
-
charset = node.children.find {|c| c.is_a?(Sass::Tree::CharsetNode)}
-
node.children.reject! {|c| c.is_a?(Sass::Tree::CharsetNode)}
-
node.children.unshift charset if charset
-
end
-
-
imports = Sass::Util.extract!(node.children) do |c|
-
c.is_a?(Sass::Tree::DirectiveNode) && c.value =~ /^@import /i
-
end
-
charset_and_index = Sass::Util.ruby1_8? &&
-
node.children.each_with_index.find {|c, _| c.is_a?(Sass::Tree::CharsetNode)}
-
if charset_and_index
-
index = charset_and_index.last
-
node.children = node.children[0..index] + imports + node.children[index+1..-1]
-
else
-
node.children = imports + node.children
-
end
-
end
-
-
return node, @extends
-
rescue Sass::SyntaxError => e
-
e.sass_template ||= node.template
-
raise e
-
end
-
-
# Registers an extension in the `@extends` subset map.
-
1
def visit_extend(node)
-
node.resolved_selector.members.each do |seq|
-
if seq.members.size > 1
-
raise Sass::SyntaxError.new("Can't extend #{seq.to_a.join}: can't extend nested selectors")
-
end
-
-
sseq = seq.members.first
-
if !sseq.is_a?(Sass::Selector::SimpleSequence)
-
raise Sass::SyntaxError.new("Can't extend #{seq.to_a.join}: invalid selector")
-
end
-
-
sel = sseq.members
-
parent.resolved_rules.members.each do |seq|
-
if !seq.members.last.is_a?(Sass::Selector::SimpleSequence)
-
raise Sass::SyntaxError.new("#{seq} can't extend: invalid selector")
-
end
-
-
@extends[sel] = seq
-
end
-
end
-
-
[]
-
end
-
-
# Modifies exception backtraces to include the imported file.
-
1
def visit_import(node)
-
# Don't use #visit_children to avoid adding the import node to the list of parents.
-
node.children.map {|c| visit(c)}.flatten
-
rescue Sass::SyntaxError => e
-
e.modify_backtrace(:filename => node.children.first.filename)
-
e.add_backtrace(:filename => node.filename, :line => node.line)
-
raise e
-
end
-
-
# Bubbles the `@media` directive up through RuleNodes
-
# and merges it with other `@media` directives.
-
1
def visit_media(node)
-
if parent.is_a?(Sass::Tree::RuleNode)
-
new_rule = parent.dup
-
new_rule.children = node.children
-
node.children = with_parent(node) {Array(visit(new_rule))}
-
# If the last child is actually the end of the group,
-
# the parent's cssize will set it properly
-
node.children.last.group_end = false unless node.children.empty?
-
else
-
yield
-
end
-
-
media = node.children.select {|c| c.is_a?(Sass::Tree::MediaNode)}
-
node.children.reject! {|c| c.is_a?(Sass::Tree::MediaNode)}
-
media.each do |n|
-
n.query = node.query.map {|pq| n.query.map {|cq| "#{pq} and #{cq}"}}.flatten
-
end
-
(node.children.empty? ? [] : [node]) + media
-
end
-
-
# Asserts that all the mixin's children are valid in their new location.
-
1
def visit_mixin(node)
-
# Don't use #visit_children to avoid adding the mixin node to the list of parents.
-
node.children.map {|c| visit(c)}.flatten
-
rescue Sass::SyntaxError => e
-
e.modify_backtrace(:mixin => node.name, :filename => node.filename, :line => node.line)
-
e.add_backtrace(:filename => node.filename, :line => node.line)
-
raise e
-
end
-
-
# Converts nested properties into flat properties
-
# and updates the indentation of the prop node based on the nesting level.
-
1
def visit_prop(node)
-
if parent.is_a?(Sass::Tree::PropNode)
-
node.resolved_name = "#{parent.resolved_name}-#{node.resolved_name}"
-
node.tabs = parent.tabs + (parent.resolved_value.empty? ? 0 : 1) if node.style == :nested
-
end
-
-
yield
-
-
result = node.children.dup
-
if !node.resolved_value.empty? || node.children.empty?
-
node.send(:check!)
-
result.unshift(node)
-
end
-
-
result
-
end
-
-
# Resolves parent references and nested selectors,
-
# and updates the indentation of the rule node based on the nesting level.
-
1
def visit_rule(node)
-
parent_resolved_rules = parent.is_a?(Sass::Tree::RuleNode) ? parent.resolved_rules : nil
-
# It's possible for resolved_rules to be set if we've duplicated this node during @media bubbling
-
node.resolved_rules ||= node.parsed_rules.resolve_parent_refs(parent_resolved_rules)
-
-
yield
-
-
rules = node.children.select {|c| c.is_a?(Sass::Tree::RuleNode) || c.is_a?(Sass::Tree::MediaNode)}
-
props = node.children.reject {|c| c.is_a?(Sass::Tree::RuleNode) || c.is_a?(Sass::Tree::MediaNode) || c.invisible?}
-
-
unless props.empty?
-
node.children = props
-
rules.each {|r| r.tabs += 1} if node.style == :nested
-
rules.unshift(node)
-
end
-
-
rules.last.group_end = true unless parent.is_a?(Sass::Tree::RuleNode) || rules.empty?
-
-
rules
-
end
-
end
-
# A visitor for copying the full structure of a Sass tree.
-
1
class Sass::Tree::Visitors::DeepCopy < Sass::Tree::Visitors::Base
-
1
protected
-
-
1
def visit(node)
-
super(node.dup)
-
end
-
-
1
def visit_children(parent)
-
parent.children = parent.children.map {|c| visit(c)}
-
parent
-
end
-
-
1
def visit_debug(node)
-
node.expr = node.expr.deep_copy
-
yield
-
end
-
-
1
def visit_each(node)
-
node.list = node.list.deep_copy
-
yield
-
end
-
-
1
def visit_extend(node)
-
node.selector = node.selector.map {|c| c.is_a?(Sass::Script::Node) ? c.deep_copy : c}
-
yield
-
end
-
-
1
def visit_for(node)
-
node.from = node.from.deep_copy
-
node.to = node.to.deep_copy
-
yield
-
end
-
-
1
def visit_function(node)
-
node.args = node.args.map {|k, v| [k.deep_copy, v && v.deep_copy]}
-
yield
-
end
-
-
1
def visit_if(node)
-
node.expr = node.expr.deep_copy if node.expr
-
node.else = visit(node.else) if node.else
-
yield
-
end
-
-
1
def visit_mixindef(node)
-
node.args = node.args.map {|k, v| [k.deep_copy, v && v.deep_copy]}
-
yield
-
end
-
-
1
def visit_mixin(node)
-
node.args = node.args.map {|a| a.deep_copy}
-
node.keywords = Hash[node.keywords.map {|k, v| [k, v.deep_copy]}]
-
yield
-
end
-
-
1
def visit_prop(node)
-
node.name = node.name.map {|c| c.is_a?(Sass::Script::Node) ? c.deep_copy : c}
-
node.value = node.value.deep_copy
-
yield
-
end
-
-
1
def visit_return(node)
-
node.expr = node.expr.deep_copy
-
yield
-
end
-
-
1
def visit_rule(node)
-
node.rule = node.rule.map {|c| c.is_a?(Sass::Script::Node) ? c.deep_copy : c}
-
yield
-
end
-
-
1
def visit_variable(node)
-
node.expr = node.expr.deep_copy
-
yield
-
end
-
-
1
def visit_warn(node)
-
node.expr = node.expr.deep_copy
-
yield
-
end
-
-
1
def visit_while(node)
-
node.expr = node.expr.deep_copy
-
yield
-
end
-
end
-
# A visitor for converting a dynamic Sass tree into a static Sass tree.
-
1
class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
-
# @param root [Tree::Node] The root node of the tree to visit.
-
# @param environment [Sass::Environment] The lexical environment.
-
# @return [Tree::Node] The resulting tree of static nodes.
-
1
def self.visit(root, environment = Sass::Environment.new)
-
new(environment).send(:visit, root)
-
end
-
-
1
protected
-
-
1
def initialize(env)
-
@environment = env
-
end
-
-
# If an exception is raised, this add proper metadata to the backtrace.
-
1
def visit(node)
-
super(node.dup)
-
rescue Sass::SyntaxError => e
-
e.modify_backtrace(:filename => node.filename, :line => node.line)
-
raise e
-
end
-
-
# Keeps track of the current environment.
-
1
def visit_children(parent)
-
with_environment Sass::Environment.new(@environment) do
-
parent.children = super.flatten
-
parent
-
end
-
end
-
-
# Runs a block of code with the current environment replaced with the given one.
-
#
-
# @param env [Sass::Environment] The new environment for the duration of the block.
-
# @yield A block in which the environment is set to `env`.
-
# @return [Object] The return value of the block.
-
1
def with_environment(env)
-
old_env, @environment = @environment, env
-
yield
-
ensure
-
@environment = old_env
-
end
-
-
# Sets the options on the environment if this is the top-level root.
-
1
def visit_root(node)
-
@environment.options = node.options if @environment.options.nil? || @environment.options.empty?
-
yield
-
rescue Sass::SyntaxError => e
-
e.sass_template ||= node.template
-
raise e
-
end
-
-
# Removes this node from the tree if it's a silent comment.
-
1
def visit_comment(node)
-
return [] if node.invisible?
-
check_for_loud_silent_comment node
-
check_for_comment_interp node
-
node.resolved_value = run_interp_no_strip(node.value)
-
node.resolved_value.gsub!(/\\([\\#])/, '\1')
-
node
-
end
-
-
# Prints the expression to STDERR.
-
1
def visit_debug(node)
-
res = node.expr.perform(@environment)
-
res = res.value if res.is_a?(Sass::Script::String)
-
if node.filename
-
$stderr.puts "#{node.filename}:#{node.line} DEBUG: #{res}"
-
else
-
$stderr.puts "Line #{node.line} DEBUG: #{res}"
-
end
-
[]
-
end
-
-
# Runs the child nodes once for each value in the list.
-
1
def visit_each(node)
-
list = node.list.perform(@environment)
-
-
with_environment Sass::Environment.new(@environment) do
-
list.to_a.map do |v|
-
@environment.set_local_var(node.var, v)
-
node.children.map {|c| visit(c)}
-
end.flatten
-
end
-
end
-
-
# Runs SassScript interpolation in the selector,
-
# and then parses the result into a {Sass::Selector::CommaSequence}.
-
1
def visit_extend(node)
-
parser = Sass::SCSS::CssParser.new(run_interp(node.selector), node.filename, node.line)
-
node.resolved_selector = parser.parse_selector
-
node
-
end
-
-
# Runs the child nodes once for each time through the loop, varying the variable each time.
-
1
def visit_for(node)
-
from = node.from.perform(@environment)
-
to = node.to.perform(@environment)
-
from.assert_int!
-
to.assert_int!
-
-
to = to.coerce(from.numerator_units, from.denominator_units)
-
range = Range.new(from.to_i, to.to_i, node.exclusive)
-
-
with_environment Sass::Environment.new(@environment) do
-
range.map do |i|
-
@environment.set_local_var(node.var,
-
Sass::Script::Number.new(i, from.numerator_units, from.denominator_units))
-
node.children.map {|c| visit(c)}
-
end.flatten
-
end
-
end
-
-
# Loads the function into the environment.
-
1
def visit_function(node)
-
@environment.set_function(node.name,
-
Sass::Callable.new(node.name, node.args, @environment, node.children))
-
[]
-
end
-
-
# Runs the child nodes if the conditional expression is true;
-
# otherwise, tries the else nodes.
-
1
def visit_if(node)
-
if node.expr.nil? || node.expr.perform(@environment).to_bool
-
yield
-
node.children
-
elsif node.else
-
visit(node.else)
-
else
-
[]
-
end
-
end
-
-
# Returns a static DirectiveNode if this is importing a CSS file,
-
# or parses and includes the imported Sass file.
-
1
def visit_import(node)
-
if path = node.css_import?
-
return Sass::Tree::DirectiveNode.new("@import url(#{path})")
-
end
-
file = node.imported_file
-
handle_import_loop!(node) if @environment.files_in_use.include?(file.options[:filename])
-
-
@environment.push_frame(:filename => node.filename, :line => node.line)
-
root = file.to_tree
-
Sass::Tree::Visitors::CheckNesting.visit(root)
-
node.children = root.children.map {|c| visit(c)}.flatten
-
node
-
rescue Sass::SyntaxError => e
-
e.modify_backtrace(:filename => node.imported_file.options[:filename])
-
e.add_backtrace(:filename => node.filename, :line => node.line)
-
raise e
-
ensure
-
@environment.pop_frame
-
end
-
-
# Loads a mixin into the environment.
-
1
def visit_mixindef(node)
-
@environment.set_mixin(node.name,
-
Sass::Callable.new(node.name, node.args, @environment, node.children))
-
[]
-
end
-
-
# Runs a mixin.
-
1
def visit_mixin(node)
-
handle_include_loop!(node) if @environment.mixins_in_use.include?(node.name)
-
-
original_env = @environment
-
original_env.push_frame(:filename => node.filename, :line => node.line)
-
original_env.prepare_frame(:mixin => node.name)
-
raise Sass::SyntaxError.new("Undefined mixin '#{node.name}'.") unless mixin = @environment.mixin(node.name)
-
-
passed_args = node.args.dup
-
passed_keywords = node.keywords.dup
-
-
raise Sass::SyntaxError.new(<<END.gsub("\n", "")) if mixin.args.size < passed_args.size
-
Mixin #{node.name} takes #{mixin.args.size} argument#{'s' if mixin.args.size != 1}
-
but #{node.args.size} #{node.args.size == 1 ? 'was' : 'were'} passed.
-
END
-
-
passed_keywords.each do |name, value|
-
# TODO: Make this fast
-
unless mixin.args.find {|(var, default)| var.underscored_name == name}
-
raise Sass::SyntaxError.new("Mixin #{node.name} doesn't have an argument named $#{name}")
-
end
-
end
-
-
environment = mixin.args.zip(passed_args).
-
inject(Sass::Environment.new(mixin.environment)) do |env, ((var, default), value)|
-
env.set_local_var(var.name,
-
if value
-
value.perform(@environment)
-
elsif kv = passed_keywords[var.underscored_name]
-
kv.perform(@environment)
-
elsif default
-
default.perform(env)
-
end)
-
raise Sass::SyntaxError.new("Mixin #{node.name} is missing parameter #{var.inspect}.") unless env.var(var.name)
-
env
-
end
-
-
with_environment(environment) {node.children = mixin.tree.map {|c| visit(c)}.flatten}
-
node
-
rescue Sass::SyntaxError => e
-
if original_env # Don't add backtrace info if this is an @include loop
-
e.modify_backtrace(:mixin => node.name, :line => node.line)
-
e.add_backtrace(:line => node.line)
-
end
-
raise e
-
ensure
-
original_env.pop_frame if original_env
-
end
-
-
# Runs any SassScript that may be embedded in a property.
-
1
def visit_prop(node)
-
node.resolved_name = run_interp(node.name)
-
val = node.value.perform(@environment)
-
node.resolved_value = val.to_s
-
yield
-
end
-
-
# Returns the value of the expression.
-
1
def visit_return(node)
-
throw :_sass_return, node.expr.perform(@environment)
-
end
-
-
# Runs SassScript interpolation in the selector,
-
# and then parses the result into a {Sass::Selector::CommaSequence}.
-
1
def visit_rule(node)
-
rule = node.rule
-
rule = rule.map {|e| e.is_a?(String) && e != ' ' ? e.strip : e} if node.style == :compressed
-
parser = Sass::SCSS::StaticParser.new(run_interp(node.rule), node.filename, node.line)
-
node.parsed_rules ||= parser.parse_selector
-
if node.options[:trace_selectors]
-
@environment.push_frame(:filename => node.filename, :line => node.line)
-
node.stack_trace = @environment.stack_trace
-
@environment.pop_frame
-
end
-
yield
-
end
-
-
# Loads the new variable value into the environment.
-
1
def visit_variable(node)
-
return [] if node.guarded && !@environment.var(node.name).nil?
-
val = node.expr.perform(@environment)
-
@environment.set_var(node.name, val)
-
[]
-
end
-
-
# Prints the expression to STDERR with a stylesheet trace.
-
1
def visit_warn(node)
-
@environment.push_frame(:filename => node.filename, :line => node.line)
-
res = node.expr.perform(@environment)
-
res = res.value if res.is_a?(Sass::Script::String)
-
msg = "WARNING: #{res}\n "
-
msg << @environment.stack_trace.join("\n ")
-
# JRuby doesn't automatically add a newline for #warn
-
msg << (RUBY_PLATFORM =~ /java/ ? "\n\n" : "\n")
-
Sass::Util.sass_warn msg
-
[]
-
ensure
-
@environment.pop_frame
-
end
-
-
# Runs the child nodes until the continuation expression becomes false.
-
1
def visit_while(node)
-
children = []
-
with_environment Sass::Environment.new(@environment) do
-
children += node.children.map {|c| visit(c)} while node.expr.perform(@environment).to_bool
-
end
-
children.flatten
-
end
-
-
1
def visit_directive(node)
-
if node.value['#{']
-
if node.value =~ /^@import (?!url\()/
-
Sass::Util.sass_warn <<WARNING
-
DEPRECATION WARNING on line #{node.line}#{" of #{node.filename}" if node.filename}:
-
@import directives using \#{} interpolation will need to use url() in Sass 3.2.
-
For example:
-
-
@import url("http://\#{$url}/style.css");
-
WARNING
-
end
-
node.value = run_interp(Sass::Engine.parse_interp(node.value, node.line, 0, node.options))
-
end
-
yield
-
node
-
end
-
-
1
private
-
-
1
def run_interp_no_strip(text)
-
text.map do |r|
-
next r if r.is_a?(String)
-
val = r.perform(@environment)
-
# Interpolated strings should never render with quotes
-
next val.value if val.is_a?(Sass::Script::String)
-
val.to_s
-
end.join
-
end
-
-
1
def run_interp(text)
-
run_interp_no_strip(text).strip
-
end
-
-
1
def handle_include_loop!(node)
-
msg = "An @include loop has been found:"
-
mixins = @environment.stack.map {|s| s[:mixin]}.compact
-
raise Sass::SyntaxError.new("#{msg} #{node.name} includes itself") if mixins.size == 1
-
-
mixins << node.name
-
msg << "\n" << Sass::Util.enum_cons(mixins, 2).map do |m1, m2|
-
" #{m1} includes #{m2}"
-
end.join("\n")
-
raise Sass::SyntaxError.new(msg)
-
end
-
-
1
def handle_import_loop!(node)
-
msg = "An @import loop has been found:"
-
files = @environment.stack.map {|s| s[:filename]}.compact
-
if node.filename == node.imported_file.options[:filename]
-
raise Sass::SyntaxError.new("#{msg} #{node.filename} imports itself")
-
end
-
-
files << node.filename << node.imported_file.options[:filename]
-
msg << "\n" << Sass::Util.enum_cons(files, 2).map do |m1, m2|
-
" #{m1} imports #{m2}"
-
end.join("\n")
-
raise Sass::SyntaxError.new(msg)
-
end
-
-
1
def check_for_loud_silent_comment(node)
-
return unless node.loud && node.silent
-
Sass::Util.sass_warn <<MESSAGE
-
WARNING:
-
On line #{node.line}#{" of '#{node.filename}'" if node.filename}
-
`//` comments will no longer be allowed to use the `!` flag in Sass 3.2.
-
Please change to `/*` comments.
-
MESSAGE
-
end
-
-
1
def check_for_comment_interp(node)
-
return if node.loud
-
node.value.each do |e|
-
next unless e.is_a?(String)
-
e.scan(/(\\*)#\{/) do |esc|
-
Sass::Util.sass_warn <<MESSAGE if esc.first.size.even?
-
WARNING:
-
On line #{node.line}#{" of '#{node.filename}'" if node.filename}
-
Comments will evaluate the contents of interpolations (\#{ ... }) in Sass 3.2.
-
Please escape the interpolation by adding a backslash before the `#`.
-
MESSAGE
-
return
-
end
-
end
-
end
-
end
-
# A visitor for setting options on the Sass tree
-
1
class Sass::Tree::Visitors::SetOptions < Sass::Tree::Visitors::Base
-
# @param root [Tree::Node] The root node of the tree to visit.
-
# @param options [{Symbol => Object}] The options has to set.
-
1
def self.visit(root, options); new(options).send(:visit, root); end
-
-
1
protected
-
-
1
def initialize(options)
-
@options = options
-
end
-
-
1
def visit(node)
-
node.instance_variable_set('@options', @options)
-
super
-
end
-
-
1
def visit_debug(node)
-
node.expr.options = @options
-
yield
-
end
-
-
1
def visit_each(node)
-
node.list.options = @options
-
yield
-
end
-
-
1
def visit_extend(node)
-
node.selector.each {|c| c.options = @options if c.is_a?(Sass::Script::Node)}
-
yield
-
end
-
-
1
def visit_for(node)
-
node.from.options = @options
-
node.to.options = @options
-
yield
-
end
-
-
1
def visit_function(node)
-
node.args.each do |k, v|
-
k.options = @options
-
v.options = @options if v
-
end
-
yield
-
end
-
-
1
def visit_if(node)
-
node.expr.options = @options if node.expr
-
visit(node.else) if node.else
-
yield
-
end
-
-
1
def visit_mixindef(node)
-
node.args.each do |k, v|
-
k.options = @options
-
v.options = @options if v
-
end
-
yield
-
end
-
-
1
def visit_mixin(node)
-
node.args.each {|a| a.options = @options}
-
node.keywords.each {|k, v| v.options = @options}
-
yield
-
end
-
-
1
def visit_prop(node)
-
node.name.each {|c| c.options = @options if c.is_a?(Sass::Script::Node)}
-
node.value.options = @options
-
yield
-
end
-
-
1
def visit_return(node)
-
node.expr.options = @options
-
yield
-
end
-
-
1
def visit_rule(node)
-
node.rule.each {|c| c.options = @options if c.is_a?(Sass::Script::Node)}
-
yield
-
end
-
-
1
def visit_variable(node)
-
node.expr.options = @options
-
yield
-
end
-
-
1
def visit_warn(node)
-
node.expr.options = @options
-
yield
-
end
-
-
1
def visit_while(node)
-
node.expr.options = @options
-
yield
-
end
-
end
-
# A visitor for converting a Sass tree into CSS.
-
1
class Sass::Tree::Visitors::ToCss < Sass::Tree::Visitors::Base
-
1
protected
-
-
1
def initialize
-
@tabs = 0
-
end
-
-
1
def visit(node)
-
super
-
rescue Sass::SyntaxError => e
-
e.modify_backtrace(:filename => node.filename, :line => node.line)
-
raise e
-
end
-
-
1
def with_tabs(tabs)
-
old_tabs, @tabs = @tabs, tabs
-
yield
-
ensure
-
@tabs = old_tabs
-
end
-
-
1
def visit_root(node)
-
result = String.new
-
node.children.each do |child|
-
next if child.invisible?
-
child_str = visit(child)
-
result << child_str + (node.style == :compressed ? '' : "\n")
-
end
-
result.rstrip!
-
return "" if result.empty?
-
result << "\n"
-
unless Sass::Util.ruby1_8? || result.ascii_only?
-
if node.children.first.is_a?(Sass::Tree::CharsetNode)
-
begin
-
encoding = node.children.first.name
-
# Default to big-endian encoding, because we have to decide somehow
-
encoding << 'BE' if encoding =~ /\Autf-(16|32)\Z/i
-
result = result.encode(Encoding.find(encoding))
-
rescue EncodingError
-
end
-
end
-
-
result = "@charset \"#{result.encoding.name}\";#{
-
node.style == :compressed ? '' : "\n"
-
}".encode(result.encoding) + result
-
end
-
result
-
rescue Sass::SyntaxError => e
-
e.sass_template ||= node.template
-
raise e
-
end
-
-
1
def visit_charset(node)
-
"@charset \"#{node.name}\";"
-
end
-
-
1
def visit_comment(node)
-
return if node.invisible?
-
spaces = (' ' * [@tabs - node.resolved_value[/^ */].size, 0].max)
-
-
content = node.resolved_value.gsub(/^/, spaces).gsub(%r{^(\s*)//(.*)$}) do |md|
-
"#{$1}/*#{$2} */"
-
end
-
content.gsub!(/\n +(\* *(?!\/))?/, ' ') if (node.style == :compact || node.style == :compressed) && !node.loud
-
content
-
end
-
-
1
def visit_directive(node)
-
was_in_directive = @in_directive
-
return node.value + ";" unless node.has_children
-
return node.value + " {}" if node.children.empty?
-
@in_directive = @in_directive || !node.is_a?(Sass::Tree::MediaNode)
-
result = if node.style == :compressed
-
"#{node.value}{"
-
else
-
"#{' ' * @tabs}#{node.value} {" + (node.style == :compact ? ' ' : "\n")
-
end
-
was_prop = false
-
first = true
-
node.children.each do |child|
-
next if child.invisible?
-
if node.style == :compact
-
if child.is_a?(Sass::Tree::PropNode)
-
with_tabs(first || was_prop ? 0 : @tabs + 1) {result << visit(child) << ' '}
-
else
-
result[-1] = "\n" if was_prop
-
rendered = with_tabs(@tabs + 1) {visit(child).dup}
-
rendered = rendered.lstrip if first
-
result << rendered.rstrip + "\n"
-
end
-
was_prop = child.is_a?(Sass::Tree::PropNode)
-
first = false
-
elsif node.style == :compressed
-
result << (was_prop ? ";" : "") << with_tabs(0) {visit(child)}
-
was_prop = child.is_a?(Sass::Tree::PropNode)
-
else
-
result << with_tabs(@tabs + 1) {visit(child)} + "\n"
-
end
-
end
-
result.rstrip + if node.style == :compressed
-
"}"
-
else
-
(node.style == :expanded ? "\n" : " ") + "}\n"
-
end
-
ensure
-
@in_directive = was_in_directive
-
end
-
-
1
def visit_media(node)
-
str = with_tabs(@tabs + node.tabs) {visit_directive(node)}
-
str.gsub!(/\n\Z/, '') unless node.style == :compressed || node.group_end
-
str
-
end
-
-
1
def visit_prop(node)
-
tab_str = ' ' * (@tabs + node.tabs)
-
if node.style == :compressed
-
"#{tab_str}#{node.resolved_name}:#{node.resolved_value}"
-
else
-
"#{tab_str}#{node.resolved_name}: #{node.resolved_value};"
-
end
-
end
-
-
1
def visit_rule(node)
-
with_tabs(@tabs + node.tabs) do
-
rule_separator = node.style == :compressed ? ',' : ', '
-
line_separator =
-
case node.style
-
when :nested, :expanded; "\n"
-
when :compressed; ""
-
else; " "
-
end
-
rule_indent = ' ' * @tabs
-
per_rule_indent, total_indent = [:nested, :expanded].include?(node.style) ? [rule_indent, ''] : ['', rule_indent]
-
-
joined_rules = node.resolved_rules.members.map do |seq|
-
rule_part = seq.to_a.join
-
if node.style == :compressed
-
rule_part.gsub!(/([^,])\s*\n\s*/m, '\1 ')
-
rule_part.gsub!(/\s*([,+>])\s*/m, '\1')
-
rule_part.strip!
-
end
-
rule_part
-
end.join(rule_separator)
-
-
joined_rules.sub!(/\A\s*/, per_rule_indent)
-
joined_rules.gsub!(/\s*\n\s*/, "#{line_separator}#{per_rule_indent}")
-
total_rule = total_indent << joined_rules
-
-
to_return = ''
-
old_spaces = ' ' * @tabs
-
spaces = ' ' * (@tabs + 1)
-
if node.style != :compressed
-
if node.options[:debug_info] && !@in_directive
-
to_return << visit(debug_info_rule(node.debug_info, node.options)) << "\n"
-
elsif node.options[:trace_selectors]
-
to_return << "#{old_spaces}/* "
-
to_return << node.stack_trace.join("\n #{old_spaces}")
-
to_return << " */\n"
-
elsif node.options[:line_comments]
-
to_return << "#{old_spaces}/* line #{node.line}"
-
-
if node.filename
-
relative_filename = if node.options[:css_filename]
-
begin
-
Pathname.new(node.filename).relative_path_from(
-
Pathname.new(File.dirname(node.options[:css_filename]))).to_s
-
rescue ArgumentError
-
nil
-
end
-
end
-
relative_filename ||= node.filename
-
to_return << ", #{relative_filename}"
-
end
-
-
to_return << " */\n"
-
end
-
end
-
-
if node.style == :compact
-
properties = with_tabs(0) {node.children.map {|a| visit(a)}.join(' ')}
-
to_return << "#{total_rule} { #{properties} }#{"\n" if node.group_end}"
-
elsif node.style == :compressed
-
properties = with_tabs(0) {node.children.map {|a| visit(a)}.join(';')}
-
to_return << "#{total_rule}{#{properties}}"
-
else
-
properties = with_tabs(@tabs + 1) {node.children.map {|a| visit(a)}.join("\n")}
-
end_props = (node.style == :expanded ? "\n" + old_spaces : ' ')
-
to_return << "#{total_rule} {\n#{properties}#{end_props}}#{"\n" if node.group_end}"
-
end
-
-
to_return
-
end
-
end
-
-
1
private
-
-
1
def debug_info_rule(debug_info, options)
-
node = Sass::Tree::DirectiveNode.new("@media -sass-debug-info")
-
Sass::Util.hash_to_a(debug_info.map {|k, v| [k.to_s, v.to_s]}).each do |k, v|
-
rule = Sass::Tree::RuleNode.new([""])
-
rule.resolved_rules = Sass::Selector::CommaSequence.new(
-
[Sass::Selector::Sequence.new(
-
[Sass::Selector::SimpleSequence.new(
-
[Sass::Selector::Element.new(k.to_s.gsub(/[^\w-]/, "\\\\\\0"), nil)])
-
])
-
])
-
prop = Sass::Tree::PropNode.new([""], Sass::Script::String.new(''), :new)
-
prop.resolved_name = "font-family"
-
prop.resolved_value = Sass::SCSS::RX.escape_ident(v.to_s)
-
rule << prop
-
node << rule
-
end
-
node.options = options.merge(:debug_info => false, :line_comments => false, :style => :compressed)
-
node
-
end
-
end
-
1
module Sass
-
1
module Tree
-
# A dynamic node representing a Sass `@warn` statement.
-
#
-
# @see Sass::Tree
-
1
class WarnNode < Node
-
# The expression to print.
-
# @return [Script::Node]
-
1
attr_accessor :expr
-
-
# @param expr [Script::Node] The expression to print
-
1
def initialize(expr)
-
@expr = expr
-
super()
-
end
-
end
-
end
-
end
-
1
require 'sass/tree/node'
-
-
1
module Sass::Tree
-
# A dynamic node representing a Sass `@while` loop.
-
#
-
# @see Sass::Tree
-
1
class WhileNode < Node
-
# The parse tree for the continuation expression.
-
# @return [Script::Node]
-
1
attr_accessor :expr
-
-
# @param expr [Script::Node] See \{#expr}
-
1
def initialize(expr)
-
@expr = expr
-
super()
-
end
-
end
-
end
-
1
require 'erb'
-
1
require 'set'
-
1
require 'enumerator'
-
1
require 'stringio'
-
1
require 'rbconfig'
-
-
1
require 'sass/root'
-
1
require 'sass/util/subset_map'
-
-
1
module Sass
-
# A module containing various useful functions.
-
1
module Util
-
1
extend self
-
-
# An array of ints representing the Ruby version number.
-
# @api public
-
4
RUBY_VERSION = ::RUBY_VERSION.split(".").map {|s| s.to_i}
-
-
# The Ruby engine we're running under. Defaults to `"ruby"`
-
# if the top-level constant is undefined.
-
# @api public
-
1
RUBY_ENGINE = defined?(::RUBY_ENGINE) ? ::RUBY_ENGINE : "ruby"
-
-
# Returns the path of a file relative to the Sass root directory.
-
#
-
# @param file [String] The filename relative to the Sass root
-
# @return [String] The filename relative to the the working directory
-
1
def scope(file)
-
5
File.join(Sass::ROOT_DIR, file)
-
end
-
-
# Converts an array of `[key, value]` pairs to a hash.
-
#
-
# @example
-
# to_hash([[:foo, "bar"], [:baz, "bang"]])
-
# #=> {:foo => "bar", :baz => "bang"}
-
# @param arr [Array<(Object, Object)>] An array of pairs
-
# @return [Hash] A hash
-
1
def to_hash(arr)
-
5
Hash[arr.compact]
-
end
-
-
# Maps the keys in a hash according to a block.
-
#
-
# @example
-
# map_keys({:foo => "bar", :baz => "bang"}) {|k| k.to_s}
-
# #=> {"foo" => "bar", "baz" => "bang"}
-
# @param hash [Hash] The hash to map
-
# @yield [key] A block in which the keys are transformed
-
# @yieldparam key [Object] The key that should be mapped
-
# @yieldreturn [Object] The new value for the key
-
# @return [Hash] The mapped hash
-
# @see #map_vals
-
# @see #map_hash
-
1
def map_keys(hash)
-
to_hash(hash.map {|k, v| [yield(k), v]})
-
end
-
-
# Maps the values in a hash according to a block.
-
#
-
# @example
-
# map_values({:foo => "bar", :baz => "bang"}) {|v| v.to_sym}
-
# #=> {:foo => :bar, :baz => :bang}
-
# @param hash [Hash] The hash to map
-
# @yield [value] A block in which the values are transformed
-
# @yieldparam value [Object] The value that should be mapped
-
# @yieldreturn [Object] The new value for the value
-
# @return [Hash] The mapped hash
-
# @see #map_keys
-
# @see #map_hash
-
1
def map_vals(hash)
-
17
to_hash(hash.map {|k, v| [k, yield(v)]})
-
end
-
-
# Maps the key-value pairs of a hash according to a block.
-
#
-
# @example
-
# map_hash({:foo => "bar", :baz => "bang"}) {|k, v| [k.to_s, v.to_sym]}
-
# #=> {"foo" => :bar, "baz" => :bang}
-
# @param hash [Hash] The hash to map
-
# @yield [key, value] A block in which the key-value pairs are transformed
-
# @yieldparam [key] The hash key
-
# @yieldparam [value] The hash value
-
# @yieldreturn [(Object, Object)] The new value for the `[key, value]` pair
-
# @return [Hash] The mapped hash
-
# @see #map_keys
-
# @see #map_vals
-
1
def map_hash(hash)
-
# Using &block here completely hoses performance on 1.8.
-
65
to_hash(hash.map {|k, v| yield k, v})
-
end
-
-
# Computes the powerset of the given array.
-
# This is the set of all subsets of the array.
-
#
-
# @example
-
# powerset([1, 2, 3]) #=>
-
# Set[Set[], Set[1], Set[2], Set[3], Set[1, 2], Set[2, 3], Set[1, 3], Set[1, 2, 3]]
-
# @param arr [Enumerable]
-
# @return [Set<Set>] The subsets of `arr`
-
1
def powerset(arr)
-
arr.inject([Set.new].to_set) do |powerset, el|
-
new_powerset = Set.new
-
powerset.each do |subset|
-
new_powerset << subset
-
new_powerset << subset + [el]
-
end
-
new_powerset
-
end
-
end
-
-
# Restricts a number to falling within a given range.
-
# Returns the number if it falls within the range,
-
# or the closest value in the range if it doesn't.
-
#
-
# @param value [Numeric]
-
# @param range [Range<Numeric>]
-
# @return [Numeric]
-
1
def restrict(value, range)
-
[[value, range.first].max, range.last].min
-
end
-
-
# Concatenates all strings that are adjacent in an array,
-
# while leaving other elements as they are.
-
#
-
# @example
-
# merge_adjacent_strings([1, "foo", "bar", 2, "baz"])
-
# #=> [1, "foobar", 2, "baz"]
-
# @param arr [Array]
-
# @return [Array] The enumerable with strings merged
-
1
def merge_adjacent_strings(arr)
-
# Optimize for the common case of one element
-
return arr if arr.size < 2
-
arr.inject([]) do |a, e|
-
if e.is_a?(String)
-
if a.last.is_a?(String)
-
a.last << e
-
else
-
a << e.dup
-
end
-
else
-
a << e
-
end
-
a
-
end
-
end
-
-
# Intersperses a value in an enumerable, as would be done with `Array#join`
-
# but without concatenating the array together afterwards.
-
#
-
# @param enum [Enumerable]
-
# @param val
-
# @return [Array]
-
1
def intersperse(enum, val)
-
enum.inject([]) {|a, e| a << e << val}[0...-1]
-
end
-
-
# Substitutes a sub-array of one array with another sub-array.
-
#
-
# @param ary [Array] The array in which to make the substitution
-
# @param from [Array] The sequence of elements to replace with `to`
-
# @param to [Array] The sequence of elements to replace `from` with
-
1
def substitute(ary, from, to)
-
res = ary.dup
-
i = 0
-
while i < res.size
-
if res[i...i+from.size] == from
-
res[i...i+from.size] = to
-
end
-
i += 1
-
end
-
res
-
end
-
-
# Destructively strips whitespace from the beginning and end
-
# of the first and last elements, respectively,
-
# in the array (if those elements are strings).
-
#
-
# @param arr [Array]
-
# @return [Array] `arr`
-
1
def strip_string_array(arr)
-
arr.first.lstrip! if arr.first.is_a?(String)
-
arr.last.rstrip! if arr.last.is_a?(String)
-
arr
-
end
-
-
# Return an array of all possible paths through the given arrays.
-
#
-
# @param arrs [Array<Array>]
-
# @return [Array<Arrays>]
-
#
-
# @example
-
# paths([[1, 2], [3, 4], [5]]) #=>
-
# # [[1, 3, 5],
-
# # [2, 3, 5],
-
# # [1, 4, 5],
-
# # [2, 4, 5]]
-
1
def paths(arrs)
-
arrs.inject([[]]) do |paths, arr|
-
flatten(arr.map {|e| paths.map {|path| path + [e]}}, 1)
-
end
-
end
-
-
# Computes a single longest common subsequence for `x` and `y`.
-
# If there are more than one longest common subsequences,
-
# the one returned is that which starts first in `x`.
-
#
-
# @param x [Array]
-
# @param y [Array]
-
# @yield [a, b] An optional block to use in place of a check for equality
-
# between elements of `x` and `y`.
-
# @yieldreturn [Object, nil] If the two values register as equal,
-
# this will return the value to use in the LCS array.
-
# @return [Array] The LCS
-
1
def lcs(x, y, &block)
-
x = [nil, *x]
-
y = [nil, *y]
-
block ||= proc {|a, b| a == b && a}
-
lcs_backtrace(lcs_table(x, y, &block), x, y, x.size-1, y.size-1, &block)
-
end
-
-
# Converts a Hash to an Array. This is usually identical to `Hash#to_a`,
-
# with the following exceptions:
-
#
-
# * In Ruby 1.8, `Hash#to_a` is not deterministically ordered, but this is.
-
# * In Ruby 1.9 when running tests, this is ordered in the same way it would
-
# be under Ruby 1.8 (sorted key order rather than insertion order).
-
#
-
# @param hash [Hash]
-
# @return [Array]
-
1
def hash_to_a(hash)
-
return hash.to_a unless ruby1_8? || defined?(Test::Unit)
-
return hash.sort_by {|k, v| k}
-
end
-
-
# Returns information about the caller of the previous method.
-
#
-
# @param entry [String] An entry in the `#caller` list, or a similarly formatted string
-
# @return [[String, Fixnum, (String, nil)]] An array containing the filename, line, and method name of the caller.
-
# The method name may be nil
-
1
def caller_info(entry = caller[1])
-
info = entry.scan(/^(.*?):(-?.*?)(?::.*`(.+)')?$/).first
-
info[1] = info[1].to_i
-
# This is added by Rubinius to designate a block, but we don't care about it.
-
info[2].sub!(/ \{\}\Z/, '') if info[2]
-
info
-
end
-
-
# Returns whether one version string represents a more recent version than another.
-
#
-
# @param v1 [String] A version string.
-
# @param v2 [String] Another version string.
-
# @return [Boolean]
-
1
def version_gt(v1, v2)
-
# Construct an array to make sure the shorter version is padded with nil
-
1
Array.new([v1.length, v2.length].max).zip(v1.split("."), v2.split(".")) do |_, p1, p2|
-
4
p1 ||= "0"
-
4
p2 ||= "0"
-
4
release1 = p1 =~ /^[0-9]+$/
-
4
release2 = p2 =~ /^[0-9]+$/
-
4
if release1 && release2
-
# Integer comparison if both are full releases
-
3
p1, p2 = p1.to_i, p2.to_i
-
3
next if p1 == p2
-
return p1 > p2
-
elsif !release1 && !release2
-
# String comparison if both are prereleases
-
next if p1 == p2
-
return p1 > p2
-
else
-
# If only one is a release, that one is newer
-
1
return release1
-
end
-
end
-
end
-
-
# Returns whether one version string represents the same or a more
-
# recent version than another.
-
#
-
# @param v1 [String] A version string.
-
# @param v2 [String] Another version string.
-
# @return [Boolean]
-
1
def version_geq(v1, v2)
-
1
version_gt(v1, v2) || !version_gt(v2, v1)
-
end
-
-
# Throws a NotImplementedError for an abstract method.
-
#
-
# @param obj [Object] `self`
-
# @raise [NotImplementedError]
-
1
def abstract(obj)
-
raise NotImplementedError.new("#{obj.class} must implement ##{caller_info[2]}")
-
end
-
-
# Silence all output to STDERR within a block.
-
#
-
# @yield A block in which no output will be printed to STDERR
-
1
def silence_warnings
-
the_real_stderr, $stderr = $stderr, StringIO.new
-
yield
-
ensure
-
$stderr = the_real_stderr
-
end
-
-
1
@@silence_warnings = false
-
# Silences all Sass warnings within a block.
-
#
-
# @yield A block in which no Sass warnings will be printed
-
1
def silence_sass_warnings
-
old_level, Sass.logger.log_level = Sass.logger.log_level, :error
-
yield
-
ensure
-
Sass.logger.log_level = old_level
-
end
-
-
# The same as `Kernel#warn`, but is silenced by \{#silence\_sass\_warnings}.
-
#
-
# @param msg [String]
-
1
def sass_warn(msg)
-
Sass.logger.warn(msg)
-
end
-
-
## Cross Rails Version Compatibility
-
-
# Returns the root of the Rails application,
-
# if this is running in a Rails context.
-
# Returns `nil` if no such root is defined.
-
#
-
# @return [String, nil]
-
1
def rails_root
-
if defined?(::Rails.root)
-
return ::Rails.root.to_s if ::Rails.root
-
raise "ERROR: Rails.root is nil!"
-
end
-
return RAILS_ROOT.to_s if defined?(RAILS_ROOT)
-
return nil
-
end
-
-
# Returns the environment of the Rails application,
-
# if this is running in a Rails context.
-
# Returns `nil` if no such environment is defined.
-
#
-
# @return [String, nil]
-
1
def rails_env
-
return ::Rails.env.to_s if defined?(::Rails.env)
-
return RAILS_ENV.to_s if defined?(RAILS_ENV)
-
return nil
-
end
-
-
# Returns whether this environment is using ActionPack
-
# version 3.0.0 or greater.
-
#
-
# @return [Boolean]
-
1
def ap_geq_3?
-
ap_geq?("3.0.0.beta1")
-
end
-
-
# Returns whether this environment is using ActionPack
-
# of a version greater than or equal to that specified.
-
#
-
# @param version [String] The string version number to check against.
-
# Should be greater than or equal to Rails 3,
-
# because otherwise ActionPack::VERSION isn't autoloaded
-
# @return [Boolean]
-
1
def ap_geq?(version)
-
# The ActionPack module is always loaded automatically in Rails >= 3
-
1
return false unless defined?(ActionPack) && defined?(ActionPack::VERSION) &&
-
defined?(ActionPack::VERSION::STRING)
-
-
1
version_geq(ActionPack::VERSION::STRING, version)
-
end
-
-
# Returns an ActionView::Template* class.
-
# In pre-3.0 versions of Rails, most of these classes
-
# were of the form `ActionView::TemplateFoo`,
-
# while afterwards they were of the form `ActionView;:Template::Foo`.
-
#
-
# @param name [#to_s] The name of the class to get.
-
# For example, `:Error` will return `ActionView::TemplateError`
-
# or `ActionView::Template::Error`.
-
1
def av_template_class(name)
-
return ActionView.const_get("Template#{name}") if ActionView.const_defined?("Template#{name}")
-
return ActionView::Template.const_get(name.to_s)
-
end
-
-
## Cross-OS Compatibility
-
-
# Whether or not this is running on Windows.
-
#
-
# @return [Boolean]
-
1
def windows?
-
RbConfig::CONFIG['host_os'] =~ /mswin|windows|mingw/i
-
end
-
-
# Whether or not this is running on IronRuby.
-
#
-
# @return [Boolean]
-
1
def ironruby?
-
4
RUBY_ENGINE == "ironruby"
-
end
-
-
## Cross-Ruby-Version Compatibility
-
-
# Whether or not this is running under Ruby 1.8 or lower.
-
#
-
# Note that IronRuby counts as Ruby 1.8,
-
# because it doesn't support the Ruby 1.9 encoding API.
-
#
-
# @return [Boolean]
-
1
def ruby1_8?
-
# IronRuby says its version is 1.9, but doesn't support any of the encoding APIs.
-
# We have to fall back to 1.8 behavior.
-
4
ironruby? || (Sass::Util::RUBY_VERSION[0] == 1 && Sass::Util::RUBY_VERSION[1] < 9)
-
end
-
-
# Whether or not this is running under Ruby 1.8.6 or lower.
-
# Note that lower versions are not officially supported.
-
#
-
# @return [Boolean]
-
1
def ruby1_8_6?
-
ruby1_8? && Sass::Util::RUBY_VERSION[2] < 7
-
end
-
-
# Checks that the encoding of a string is valid in Ruby 1.9
-
# and cleans up potential encoding gotchas like the UTF-8 BOM.
-
# If it's not, yields an error string describing the invalid character
-
# and the line on which it occurrs.
-
#
-
# @param str [String] The string of which to check the encoding
-
# @yield [msg] A block in which an encoding error can be raised.
-
# Only yields if there is an encoding error
-
# @yieldparam msg [String] The error message to be raised
-
# @return [String] `str`, potentially with encoding gotchas like BOMs removed
-
1
def check_encoding(str)
-
if ruby1_8?
-
return str.gsub(/\A\xEF\xBB\xBF/, '') # Get rid of the UTF-8 BOM
-
elsif str.valid_encoding?
-
# Get rid of the Unicode BOM if possible
-
if str.encoding.name =~ /^UTF-(8|16|32)(BE|LE)?$/
-
return str.gsub(Regexp.new("\\A\uFEFF".encode(str.encoding.name)), '')
-
else
-
return str
-
end
-
end
-
-
encoding = str.encoding
-
newlines = Regexp.new("\r\n|\r|\n".encode(encoding).force_encoding("binary"))
-
str.force_encoding("binary").split(newlines).each_with_index do |line, i|
-
begin
-
line.encode(encoding)
-
rescue Encoding::UndefinedConversionError => e
-
yield <<MSG.rstrip, i + 1
-
Invalid #{encoding.name} character #{e.error_char.dump}
-
MSG
-
end
-
end
-
return str
-
end
-
-
# Like {\#check\_encoding}, but also checks for a `@charset` declaration
-
# at the beginning of the file and uses that encoding if it exists.
-
#
-
# The Sass encoding rules are simple.
-
# If a `@charset` declaration exists,
-
# we assume that that's the original encoding of the document.
-
# Otherwise, we use whatever encoding Ruby has.
-
# Then we convert that to UTF-8 to process internally.
-
# The UTF-8 end result is what's returned by this method.
-
#
-
# @param str [String] The string of which to check the encoding
-
# @yield [msg] A block in which an encoding error can be raised.
-
# Only yields if there is an encoding error
-
# @yieldparam msg [String] The error message to be raised
-
# @return [(String, Encoding)] The original string encoded as UTF-8,
-
# and the source encoding of the string (or `nil` under Ruby 1.8)
-
# @raise [Encoding::UndefinedConversionError] if the source encoding
-
# cannot be converted to UTF-8
-
# @raise [ArgumentError] if the document uses an unknown encoding with `@charset`
-
1
def check_sass_encoding(str, &block)
-
return check_encoding(str, &block), nil if ruby1_8?
-
# We allow any printable ASCII characters but double quotes in the charset decl
-
bin = str.dup.force_encoding("BINARY")
-
encoding = Sass::Util::ENCODINGS_TO_CHECK.find do |enc|
-
re = Sass::Util::CHARSET_REGEXPS[enc]
-
re && bin =~ re
-
end
-
charset, bom = $1, $2
-
if charset
-
charset = charset.force_encoding(encoding).encode("UTF-8")
-
if endianness = encoding[/[BL]E$/]
-
begin
-
Encoding.find(charset + endianness)
-
charset << endianness
-
rescue ArgumentError # Encoding charset + endianness doesn't exist
-
end
-
end
-
str.force_encoding(charset)
-
elsif bom
-
str.force_encoding(encoding)
-
end
-
-
str = check_encoding(str, &block)
-
return str.encode("UTF-8"), str.encoding
-
end
-
-
1
unless ruby1_8?
-
# @private
-
1
def _enc(string, encoding)
-
string.encode(encoding).force_encoding("BINARY")
-
end
-
-
# We could automatically add in any non-ASCII-compatible encodings here,
-
# but there's not really a good way to do that
-
# without manually checking that each encoding
-
# encodes all ASCII characters properly,
-
# which takes long enough to affect the startup time of the CLI.
-
1
ENCODINGS_TO_CHECK = %w[UTF-8 UTF-16BE UTF-16LE UTF-32BE UTF-32LE]
-
-
1
CHARSET_REGEXPS = Hash.new do |h, e|
-
h[e] =
-
begin
-
# /\A(?:\uFEFF)?@charset "(.*?)"|\A(\uFEFF)/
-
Regexp.new(/\A(?:#{_enc("\uFEFF", e)})?#{
-
_enc('@charset "', e)}(.*?)#{_enc('"', e)}|\A(#{
-
_enc("\uFEFF", e)})/)
-
rescue Encoding::ConverterNotFound => _
-
nil # JRuby on Java 5 doesn't support UTF-32
-
rescue
-
# /\A@charset "(.*?)"/
-
Regexp.new(/\A#{_enc('@charset "', e)}(.*?)#{_enc('"', e)}/)
-
end
-
end
-
end
-
-
# Checks to see if a class has a given method.
-
# For example:
-
#
-
# Sass::Util.has?(:public_instance_method, String, :gsub) #=> true
-
#
-
# Method collections like `Class#instance_methods`
-
# return strings in Ruby 1.8 and symbols in Ruby 1.9 and on,
-
# so this handles checking for them in a compatible way.
-
#
-
# @param attr [#to_s] The (singular) name of the method-collection method
-
# (e.g. `:instance_methods`, `:private_methods`)
-
# @param klass [Module] The class to check the methods of which to check
-
# @param method [String, Symbol] The name of the method do check for
-
# @return [Boolean] Whether or not the given collection has the given method
-
1
def has?(attr, klass, method)
-
1
klass.send("#{attr}s").include?(ruby1_8? ? method.to_s : method.to_sym)
-
end
-
-
# A version of `Enumerable#enum_with_index` that works in Ruby 1.8 and 1.9.
-
#
-
# @param enum [Enumerable] The enumerable to get the enumerator for
-
# @return [Enumerator] The with-index enumerator
-
1
def enum_with_index(enum)
-
ruby1_8? ? enum.enum_with_index : enum.each_with_index
-
end
-
-
# A version of `Enumerable#enum_cons` that works in Ruby 1.8 and 1.9.
-
#
-
# @param enum [Enumerable] The enumerable to get the enumerator for
-
# @param n [Fixnum] The size of each cons
-
# @return [Enumerator] The consed enumerator
-
1
def enum_cons(enum, n)
-
ruby1_8? ? enum.enum_cons(n) : enum.each_cons(n)
-
end
-
-
# A version of `Enumerable#enum_slice` that works in Ruby 1.8 and 1.9.
-
#
-
# @param enum [Enumerable] The enumerable to get the enumerator for
-
# @param n [Fixnum] The size of each slice
-
# @return [Enumerator] The consed enumerator
-
1
def enum_slice(enum, n)
-
ruby1_8? ? enum.enum_slice(n) : enum.each_slice(n)
-
end
-
-
# Destructively removes all elements from an array that match a block, and
-
# returns the removed elements.
-
#
-
# @param array [Array] The array from which to remove elements.
-
# @yield [el] Called for each element.
-
# @yieldparam el [*] The element to test.
-
# @yieldreturn [Boolean] Whether or not to extract the element.
-
# @return [Array] The extracted elements.
-
1
def extract!(array)
-
out = []
-
array.reject! do |e|
-
next false unless yield e
-
out << e
-
true
-
end
-
out
-
end
-
-
# Returns the ASCII code of the given character.
-
#
-
# @param c [String] All characters but the first are ignored.
-
# @return [Fixnum] The ASCII code of `c`.
-
1
def ord(c)
-
ruby1_8? ? c[0] : c.ord
-
end
-
-
# Flattens the first `n` nested arrays in a cross-version manner.
-
#
-
# @param arr [Array] The array to flatten
-
# @param n [Fixnum] The number of levels to flatten
-
# @return [Array] The flattened array
-
1
def flatten(arr, n)
-
return arr.flatten(n) unless ruby1_8_6?
-
return arr if n == 0
-
arr.inject([]) {|res, e| e.is_a?(Array) ? res.concat(flatten(e, n - 1)) : res << e}
-
end
-
-
# Returns the hash code for a set in a cross-version manner.
-
# Aggravatingly, this is order-dependent in Ruby 1.8.6.
-
#
-
# @param set [Set]
-
# @return [Fixnum] The order-independent hashcode of `set`
-
1
def set_hash(set)
-
return set.hash unless ruby1_8_6?
-
set.map {|e| e.hash}.uniq.sort.hash
-
end
-
-
# Tests the hash-equality of two sets in a cross-version manner.
-
# Aggravatingly, this is order-dependent in Ruby 1.8.6.
-
#
-
# @param set1 [Set]
-
# @param set2 [Set]
-
# @return [Boolean] Whether or not the sets are hashcode equal
-
1
def set_eql?(set1, set2)
-
return set1.eql?(set2) unless ruby1_8_6?
-
set1.to_a.uniq.sort_by {|e| e.hash}.eql?(set2.to_a.uniq.sort_by {|e| e.hash})
-
end
-
-
# Like `Object#inspect`, but preserves non-ASCII characters rather than escaping them under Ruby 1.9.2.
-
# This is necessary so that the precompiled Haml template can be `#encode`d into `@options[:encoding]`
-
# before being evaluated.
-
#
-
# @param obj {Object}
-
# @return {String}
-
1
def inspect_obj(obj)
-
return obj.inspect unless version_geq(::RUBY_VERSION, "1.9.2")
-
return ':' + inspect_obj(obj.to_s) if obj.is_a?(Symbol)
-
return obj.inspect unless obj.is_a?(String)
-
'"' + obj.gsub(/[\x00-\x7F]+/) {|s| s.inspect[1...-1]} + '"'
-
end
-
-
# Extracts the non-string vlaues from an array containing both strings and non-strings.
-
# These values are replaced with escape sequences.
-
# This can be undone using \{#inject\_values}.
-
#
-
# This is useful e.g. when we want to do string manipulation
-
# on an interpolated string.
-
#
-
# The precise format of the resulting string is not guaranteed.
-
# However, it is guaranteed that newlines and whitespace won't be affected.
-
#
-
# @param arr [Array] The array from which values are extracted.
-
# @return [(String, Array)] The resulting string, and an array of extracted values.
-
1
def extract_values(arr)
-
values = []
-
return arr.map do |e|
-
next e.gsub('{', '{{') if e.is_a?(String)
-
values << e
-
next "{#{values.count - 1}}"
-
end.join, values
-
end
-
-
# Undoes \{#extract\_values} by transforming a string with escape sequences
-
# into an array of strings and non-string values.
-
#
-
# @param str [String] The string with escape sequences.
-
# @param values [Array] The array of values to inject.
-
# @return [Array] The array of strings and values.
-
1
def inject_values(str, values)
-
return [str.gsub('{{', '{')] if values.empty?
-
# Add an extra { so that we process the tail end of the string
-
result = (str + '{{').scan(/(.*?)(?:(\{\{)|\{(\d+)\})/m).map do |(pre, esc, n)|
-
[pre, esc ? '{' : '', n ? values[n.to_i] : '']
-
end.flatten(1)
-
result[-2] = '' # Get rid of the extra {
-
merge_adjacent_strings(result).reject {|s| s == ''}
-
end
-
-
# Allows modifications to be performed on the string form
-
# of an array containing both strings and non-strings.
-
#
-
# @param arr [Array] The array from which values are extracted.
-
# @yield [str] A block in which string manipulation can be done to the array.
-
# @yieldparam str [String] The string form of `arr`.
-
# @yieldreturn [String] The modified string.
-
# @return [Array] The modified, interpolated array.
-
1
def with_extracted_values(arr)
-
str, vals = extract_values(arr)
-
str = yield str
-
inject_values(str, vals)
-
end
-
-
## Static Method Stuff
-
-
# The context in which the ERB for \{#def\_static\_method} will be run.
-
1
class StaticConditionalContext
-
# @param set [#include?] The set of variables that are defined for this context.
-
1
def initialize(set)
-
@set = set
-
end
-
-
# Checks whether or not a variable is defined for this context.
-
#
-
# @param name [Symbol] The name of the variable
-
# @return [Boolean]
-
1
def method_missing(name, *args, &block)
-
super unless args.empty? && block.nil?
-
@set.include?(name)
-
end
-
end
-
-
1
private
-
-
# Calculates the memoization table for the Least Common Subsequence algorithm.
-
# Algorithm from [Wikipedia](http://en.wikipedia.org/wiki/Longest_common_subsequence_problem#Computing_the_length_of_the_LCS)
-
1
def lcs_table(x, y)
-
c = Array.new(x.size) {[]}
-
x.size.times {|i| c[i][0] = 0}
-
y.size.times {|j| c[0][j] = 0}
-
(1...x.size).each do |i|
-
(1...y.size).each do |j|
-
c[i][j] =
-
if yield x[i], y[j]
-
c[i-1][j-1] + 1
-
else
-
[c[i][j-1], c[i-1][j]].max
-
end
-
end
-
end
-
return c
-
end
-
-
# Computes a single longest common subsequence for arrays x and y.
-
# Algorithm from [Wikipedia](http://en.wikipedia.org/wiki/Longest_common_subsequence_problem#Reading_out_an_LCS)
-
1
def lcs_backtrace(c, x, y, i, j, &block)
-
return [] if i == 0 || j == 0
-
if v = yield(x[i], y[j])
-
return lcs_backtrace(c, x, y, i-1, j-1, &block) << v
-
end
-
-
return lcs_backtrace(c, x, y, i, j-1, &block) if c[i][j-1] > c[i-1][j]
-
return lcs_backtrace(c, x, y, i-1, j, &block)
-
end
-
end
-
end
-
-
1
require 'sass/util/multibyte_string_scanner'
-
1
require 'strscan'
-
-
1
if Sass::Util.ruby1_8?
-
Sass::Util::MultibyteStringScanner = StringScanner
-
else
-
# A wrapper of the native StringScanner class that works correctly with
-
# multibyte character encodings. The native class deals only in bytes, not
-
# characters, for methods like [#pos] and [#matched_size]. This class deals
-
# only in characters, instead.
-
1
class Sass::Util::MultibyteStringScanner < StringScanner
-
1
def self.new(str)
-
return StringScanner.new(str) if str.ascii_only?
-
super
-
end
-
-
1
def initialize(str)
-
super
-
@mb_pos = 0
-
@mb_matched_size = nil
-
@mb_last_pos = nil
-
end
-
-
1
alias_method :byte_pos, :pos
-
1
alias_method :byte_matched_size, :matched_size
-
-
1
def check(pattern); _match super; end
-
1
def check_until(pattern); _matched super; end
-
1
def getch; _forward _match super; end
-
1
def match?(pattern); _size check(pattern); end
-
1
def matched_size; @mb_matched_size; end
-
1
def peek(len); string[@mb_pos, len]; end
-
1
alias_method :peep, :peek
-
1
def pos; @mb_pos; end
-
1
alias_method :pointer, :pos
-
1
def rest_size; rest.size; end
-
1
def scan(pattern); _forward _match super; end
-
1
def scan_until(pattern); _forward _matched super; end
-
1
def skip(pattern); _size scan(pattern); end
-
1
def skip_until(pattern); _matched _size scan_until(pattern); end
-
-
1
def get_byte
-
raise "MultibyteStringScanner doesn't support #get_byte."
-
end
-
-
1
def getbyte
-
raise "MultibyteStringScanner doesn't support #getbyte."
-
end
-
-
1
def pos=(n)
-
@mb_last_pos = nil
-
-
# We set position kind of a lot during parsing, so we want it to be as
-
# efficient as possible. This is complicated by the fact that UTF-8 is a
-
# variable-length encoding, so it's difficult to find the byte length that
-
# corresponds to a given character length.
-
#
-
# Our heuristic here is to try to count the fewest possible characters. So
-
# if the new position is close to the current one, just count the
-
# characters between the two; if the new position is closer to the
-
# beginning of the string, just count the characters from there.
-
if @mb_pos - n < @mb_pos / 2
-
# New position is close to old position
-
byte_delta = @mb_pos > n ? -string[n...@mb_pos].bytesize : string[@mb_pos...n].bytesize
-
super(byte_pos + byte_delta)
-
else
-
# New position is close to BOS
-
super(string[0...n].bytesize)
-
end
-
@mb_pos = n
-
end
-
-
1
def reset
-
@mb_pos = 0
-
@mb_matched_size = nil
-
@mb_last_pos = nil
-
super
-
end
-
-
1
def scan_full(pattern, advance_pointer_p, return_string_p)
-
res = _match super(pattern, advance_pointer_p, true)
-
_forward res if advance_pointer_p
-
return res if return_string_p
-
end
-
-
1
def search_full(pattern, advance_pointer_p, return_string_p)
-
res = super(pattern, advance_pointer_p, true)
-
_forward res if advance_pointer_p
-
_matched((res if return_string_p))
-
end
-
-
1
def string=(str)
-
@mb_pos = 0
-
@mb_matched_size = nil
-
@mb_last_pos = nil
-
super
-
end
-
-
1
def terminate
-
@mb_pos = string.size
-
@mb_matched_size = nil
-
@mb_last_pos = nil
-
super
-
end
-
1
alias_method :clear, :terminate
-
-
1
def unscan
-
super
-
@mb_pos = @mb_last_pos
-
@mb_last_pos = @mb_matched_size = nil
-
end
-
-
1
private
-
-
1
def _size(str)
-
str && str.size
-
end
-
-
1
def _match(str)
-
@mb_matched_size = str && str.size
-
str
-
end
-
-
1
def _matched(res)
-
_match matched
-
res
-
end
-
-
1
def _forward(str)
-
@mb_last_pos = @mb_pos
-
@mb_pos += str.size if str
-
str
-
end
-
end
-
end
-
1
require 'set'
-
-
1
module Sass
-
1
module Util
-
# A map from sets to values.
-
# A value is \{#\[]= set} by providing a set (the "set-set") and a value,
-
# which is then recorded as corresponding to that set.
-
# Values are \{#\[] accessed} by providing a set (the "get-set")
-
# and returning all values that correspond to set-sets
-
# that are subsets of the get-set.
-
#
-
# SubsetMap preserves the order of values as they're inserted.
-
#
-
# @example
-
# ssm = SubsetMap.new
-
# ssm[Set[1, 2]] = "Foo"
-
# ssm[Set[2, 3]] = "Bar"
-
# ssm[Set[1, 2, 3]] = "Baz"
-
#
-
# ssm[Set[1, 2, 3]] #=> ["Foo", "Bar", "Baz"]
-
1
class SubsetMap
-
# Creates a new, empty SubsetMap.
-
1
def initialize
-
@hash = {}
-
@vals = []
-
end
-
-
# Whether or not this SubsetMap has any key-value pairs.
-
#
-
# @return [Boolean]
-
1
def empty?
-
@hash.empty?
-
end
-
-
# Associates a value with a set.
-
# When `set` or any of its supersets is accessed,
-
# `value` will be among the values returned.
-
#
-
# Note that if the same `set` is passed to this method multiple times,
-
# all given `value`s will be associated with that `set`.
-
#
-
# This runs in `O(n)` time, where `n` is the size of `set`.
-
#
-
# @param set [#to_set] The set to use as the map key. May not be empty.
-
# @param value [Object] The value to associate with `set`.
-
# @raise [ArgumentError] If `set` is empty.
-
1
def []=(set, value)
-
raise ArgumentError.new("SubsetMap keys may not be empty.") if set.empty?
-
-
index = @vals.size
-
@vals << value
-
set.each do |k|
-
@hash[k] ||= []
-
@hash[k] << [set, set.to_set, index]
-
end
-
end
-
-
# Returns all values associated with subsets of `set`.
-
#
-
# In the worst case, this runs in `O(m*max(n, log m))` time,
-
# where `n` is the size of `set`
-
# and `m` is the number of assocations in the map.
-
# However, unless many keys in the map overlap with `set`,
-
# `m` will typically be much smaller.
-
#
-
# @param set [Set] The set to use as the map key.
-
# @return [Array<(Object, #to_set)>] An array of pairs,
-
# where the first value is the value associated with a subset of `set`,
-
# and the second value is that subset of `set`
-
# (or whatever `#to_set` object was used to set the value)
-
# This array is in insertion order.
-
# @see #[]
-
1
def get(set)
-
res = set.map do |k|
-
next unless subsets = @hash[k]
-
subsets.map do |subenum, subset, index|
-
next unless subset.subset?(set)
-
[index, subenum]
-
end
-
end
-
res = Sass::Util.flatten(res, 1)
-
res.compact!
-
res.uniq!
-
res.sort!
-
res.map! {|i, s| [@vals[i], s]}
-
return res
-
end
-
-
# Same as \{#get}, but doesn't return the subsets of the argument
-
# for which values were found.
-
#
-
# @param set [Set] The set to use as the map key.
-
# @return [Array] The array of all values
-
# associated with subsets of `set`, in insertion order.
-
# @see #get
-
1
def [](set)
-
get(set).map {|v, _| v}
-
end
-
end
-
end
-
end
-
# This is necessary for loading Sass when Haml is required in Rails 3.
-
# Once the split is complete, we can remove it.
-
1
require File.dirname(__FILE__) + '/../sass'
-
1
require 'sass/util'
-
-
1
module Sass
-
# Handles Sass version-reporting.
-
# Sass not only reports the standard three version numbers,
-
# but its Git revision hash as well,
-
# if it was installed from Git.
-
1
module Version
-
1
include Sass::Util
-
-
# Returns a hash representing the version of Sass.
-
# The `:major`, `:minor`, and `:teeny` keys have their respective numbers as Fixnums.
-
# The `:name` key has the name of the version.
-
# The `:string` key contains a human-readable string representation of the version.
-
# The `:number` key is the major, minor, and teeny keys separated by periods.
-
# If Sass is checked out from Git, the `:rev` key will have the revision hash.
-
# For example:
-
#
-
# {
-
# :string => "2.1.0.9616393",
-
# :rev => "9616393b8924ef36639c7e82aa88a51a24d16949",
-
# :number => "2.1.0",
-
# :major => 2, :minor => 1, :teeny => 0
-
# }
-
#
-
# If a prerelease version of Sass is being used,
-
# the `:string` and `:number` fields will reflect the full version
-
# (e.g. `"2.2.beta.1"`), and the `:teeny` field will be `-1`.
-
# A `:prerelease` key will contain the name of the prerelease (e.g. `"beta"`),
-
# and a `:prerelease_number` key will contain the rerelease number.
-
# For example:
-
#
-
# {
-
# :string => "3.0.beta.1",
-
# :number => "3.0.beta.1",
-
# :major => 3, :minor => 0, :teeny => -1,
-
# :prerelease => "beta",
-
# :prerelease_number => 1
-
# }
-
#
-
# @return [{Symbol => String/Fixnum}] The version hash
-
1
def version
-
1
return @@version if defined?(@@version)
-
-
1
numbers = File.read(scope('VERSION')).strip.split('.').
-
3
map {|n| n =~ /^[0-9]+$/ ? n.to_i : n}
-
1
name = File.read(scope('VERSION_NAME')).strip
-
1
@@version = {
-
:major => numbers[0],
-
:minor => numbers[1],
-
:teeny => numbers[2],
-
:name => name
-
}
-
-
1
if numbers[3].is_a?(String)
-
@@version[:teeny] = -1
-
@@version[:prerelease] = numbers[3]
-
@@version[:prerelease_number] = numbers[4]
-
end
-
-
1
@@version[:number] = numbers.join('.')
-
1
@@version[:string] = @@version[:number].dup
-
-
1
if rev = revision_number
-
@@version[:rev] = rev
-
unless rev[0] == ?(
-
@@version[:string] << "." << rev[0...7]
-
end
-
end
-
-
1
@@version[:string] << " (#{name})"
-
1
@@version
-
end
-
-
1
private
-
-
1
def revision_number
-
1
if File.exists?(scope('REVISION'))
-
1
rev = File.read(scope('REVISION')).strip
-
1
return rev unless rev =~ /^([a-f0-9]+|\(.*\))$/ || rev == '(unknown)'
-
end
-
-
1
return unless File.exists?(scope('.git/HEAD'))
-
rev = File.read(scope('.git/HEAD')).strip
-
return rev unless rev =~ /^ref: (.*)$/
-
-
ref_name = $1
-
ref_file = scope(".git/#{ref_name}")
-
info_file = scope(".git/info/refs")
-
return File.read(ref_file).strip if File.exists?(ref_file)
-
return unless File.exists?(info_file)
-
File.open(info_file) do |f|
-
f.each do |l|
-
sha, ref = l.strip.split("\t", 2)
-
next unless ref == ref_name
-
return sha
-
end
-
end
-
return nil
-
end
-
end
-
-
1
extend Sass::Version
-
-
# A string representing the version of Sass.
-
# A more fine-grained representation is available from Sass.version.
-
# @api public
-
1
VERSION = version[:string] unless defined?(Sass::VERSION)
-
end
-
1
require 'sass/rails'
-
1
module Sass
-
1
module Rails
-
end
-
end
-
-
1
require 'sass/rails/compressor'
-
1
require 'sass/rails/logger'
-
1
require 'sass/rails/railtie'
-
1
require 'sass/rails/helpers'
-
1
require 'sass/rails/importer'
-
1
require 'sass/rails/template_handlers'
-
1
require 'sass/rails/version'
-
1
require 'sass'
-
-
1
module Sass
-
1
module Rails
-
1
class CssCompressor
-
1
def compress(css)
-
if css.count("\n") > 2
-
Sass::Engine.new(css,
-
:syntax => :scss,
-
:cache => false,
-
:read_cache => false,
-
:style => :compressed).render
-
else
-
css
-
end
-
end
-
end
-
end
-
end
-
1
module Sass
-
1
module Rails
-
1
module Helpers
-
-
1
def asset_data_url(path)
-
data = context_asset_data_uri(path.value)
-
Sass::Script::String.new(%Q{url(#{data})})
-
end
-
-
1
def asset_path(asset, kind)
-
Sass::Script::String.new(public_path(asset.value, kind.value), true)
-
end
-
-
1
def asset_url(asset, kind)
-
Sass::Script::String.new(%Q{url(#{public_path(asset.value, kind.value)})})
-
end
-
-
1
[:image, :video, :audio, :javascript, :stylesheet].each do |asset_class|
-
class_eval %Q{
-
def #{asset_class}_path(asset)
-
Sass::Script::String.new(resolver.#{asset_class}_path(asset.value), true)
-
end
-
def #{asset_class}_url(asset)
-
Sass::Script::String.new("url(" + resolver.#{asset_class}_path(asset.value) + ")")
-
end
-
5
}, __FILE__, __LINE__ - 6
-
end
-
-
1
def font_path(asset)
-
asset_path(asset, Sass::Script::String.new("font"))
-
end
-
-
1
def font_url(asset)
-
asset_url(asset, Sass::Script::String.new("font"))
-
end
-
-
1
protected
-
-
1
def resolver
-
options[:custom][:resolver]
-
end
-
-
1
def public_path(asset, kind)
-
resolver.public_path(asset, kind.pluralize)
-
end
-
-
1
def context_asset_data_uri(path)
-
resolver.context.asset_data_uri(path)
-
end
-
end
-
end
-
end
-
-
1
module Sass
-
1
module Script
-
1
module Functions
-
1
include Sass::Rails::Helpers
-
end
-
end
-
end
-
1
require 'sprockets'
-
-
1
module Sass::Rails
-
1
class Importer
-
1
GLOB = /\*|\[.+\]/
-
1
PARTIAL = /^_/
-
1
HAS_EXTENSION = /\.css(.s[ac]ss)?$/
-
-
1
SASS_EXTENSIONS = {
-
".css.sass" => :sass,
-
".css.scss" => :scss,
-
".sass" => :sass,
-
".scss" => :scss
-
}
-
1
attr_reader :context
-
-
1
def initialize(context)
-
@context = context
-
@resolver = Resolver.new(context)
-
end
-
-
1
def sass_file?(filename)
-
filename = filename.to_s
-
SASS_EXTENSIONS.keys.any?{|ext| filename[ext]}
-
end
-
-
1
def syntax(filename)
-
filename = filename.to_s
-
SASS_EXTENSIONS.each {|ext, syntax| return syntax if filename[(ext.size+2)..-1][ext]}
-
nil
-
end
-
-
1
def resolve(name, base_pathname = nil)
-
name = Pathname.new(name)
-
if base_pathname && base_pathname.to_s.size > 0
-
root = Pathname.new(context.root_path)
-
name = base_pathname.relative_path_from(root).join(name)
-
end
-
partial_name = name.dirname.join("_#{name.basename}")
-
@resolver.resolve(name) || @resolver.resolve(partial_name)
-
end
-
-
1
def find_relative(name, base, options)
-
base_pathname = Pathname.new(base)
-
if name =~ GLOB
-
glob_imports(name, base_pathname, options)
-
elsif pathname = resolve(name, base_pathname.dirname)
-
context.depend_on(pathname)
-
if sass_file?(pathname)
-
Sass::Engine.new(pathname.read, options.merge(:filename => pathname.to_s, :importer => self, :syntax => syntax(pathname)))
-
else
-
Sass::Engine.new(@resolver.process(pathname), options.merge(:filename => pathname.to_s, :importer => self, :syntax => :scss))
-
end
-
else
-
nil
-
end
-
end
-
-
1
def find(name, options)
-
if name =~ GLOB
-
nil # globs must be relative
-
elsif pathname = resolve(name)
-
context.depend_on(pathname)
-
if sass_file?(pathname)
-
Sass::Engine.new(pathname.read, options.merge(:filename => pathname.to_s, :importer => self, :syntax => syntax(pathname)))
-
else
-
Sass::Engine.new(@resolver.process(pathname), options.merge(:filename => pathname.to_s, :importer => self, :syntax => :scss))
-
end
-
else
-
nil
-
end
-
end
-
-
1
def each_globbed_file(glob, base_pathname, options)
-
Dir["#{base_pathname}/#{glob}"].sort.each do |filename|
-
next if filename == options[:filename]
-
yield filename if File.directory?(filename) || context.asset_requirable?(filename)
-
end
-
end
-
-
1
def glob_imports(glob, base_pathname, options)
-
contents = ""
-
each_globbed_file(glob, base_pathname.dirname, options) do |filename|
-
if File.directory?(filename)
-
context.depend_on(filename)
-
elsif context.asset_requirable?(filename)
-
context.depend_on(filename)
-
contents << "@import #{Pathname.new(filename).relative_path_from(base_pathname.dirname).to_s.inspect};\n"
-
end
-
end
-
return nil if contents.empty?
-
Sass::Engine.new(contents, options.merge(
-
:filename => base_pathname.to_s,
-
:importer => self,
-
:syntax => :scss
-
))
-
end
-
-
1
def mtime(name, options)
-
if name =~ GLOB && options[:filename]
-
mtime = nil
-
each_globbed_file(name, Pathname.new(options[:filename]).dirname, options) do |p|
-
mtime ||= File.mtime(p)
-
mtime = [mtime, File.mtime(p)].max
-
end
-
mtime
-
elsif pathname = resolve(name)
-
pathname.mtime
-
end
-
end
-
-
1
def key(name, options)
-
["Sprockets:" + File.dirname(File.expand_path(name)), File.basename(name)]
-
end
-
-
1
def to_s
-
"Sass::Rails::Importer(#{context.pathname})"
-
end
-
-
end
-
-
end
-
1
require 'sass/logger'
-
-
1
module Sass
-
1
module Rails
-
1
class Logger < Sass::Logger::Base
-
1
def _log(level, message)
-
-
case level
-
when :trace, :debug
-
::Rails.logger.debug message
-
when :warn
-
::Rails.logger.warn message
-
when :error
-
::Rails.logger.error message
-
when :info
-
::Rails.logger.info message
-
end
-
end
-
end
-
end
-
end
-
1
require 'sprockets/railtie'
-
-
1
module Sass::Rails
-
1
class Railtie < ::Rails::Railtie
-
1
module SassContext
-
1
attr_accessor :sass_config
-
end
-
-
1
config.sass = ActiveSupport::OrderedOptions.new
-
-
# Establish static configuration defaults
-
# Emit scss files during stylesheet generation of scaffold
-
1
config.sass.preferred_syntax = :scss
-
# Use expanded output instead of the sass default of :nested
-
1
config.sass.style = :expanded
-
# Write sass cache files for performance
-
1
config.sass.cache = true
-
# Read sass cache files for performance
-
1
config.sass.read_cache = true
-
# Display line comments above each selector as a debugging aid
-
1
config.sass.line_comments = true
-
# Initialize the load paths to an empty array
-
1
config.sass.load_paths = []
-
# Send Sass logs to Rails.logger
-
1
config.sass.logger = Sass::Rails::Logger.new
-
-
# Set the default stylesheet engine
-
# It can be overridedden by passing:
-
# --stylesheet_engine=sass
-
# to the rails generate command
-
1
config.app_generators.stylesheet_engine config.sass.preferred_syntax
-
-
1
config.before_initialize do
-
require 'sass'
-
-
# Assume dependency on sprockets, not on app.config.assets enabled.
-
if defined?(Sprockets::Engines)
-
Sprockets::Engines #force autoloading
-
Sprockets.register_engine '.sass', Sass::Rails::SassTemplate
-
Sprockets.register_engine '.scss', Sass::Rails::ScssTemplate
-
end
-
end
-
-
# Remove the sass middleware if it gets inadvertently enabled by applications.
-
1
config.after_initialize do |app|
-
app.config.middleware.delete(Sass::Plugin::Rack) if defined?(Sass::Plugin::Rack)
-
end
-
-
1
initializer :setup_sass, :group => :all do |app|
-
# Only emit one kind of syntax because though we have registered two kinds of generators
-
syntax = app.config.sass.preferred_syntax.to_sym
-
alt_syntax = syntax == :sass ? "scss" : "sass"
-
app.config.generators.hide_namespace alt_syntax
-
-
# Override stylesheet engine to the preferred syntax
-
config.app_generators.stylesheet_engine syntax
-
-
# Set the sass cache location
-
config.sass.cache_location = File.join(Rails.root, "tmp/cache/sass")
-
-
# Establish configuration defaults that are evironmental in nature
-
if config.sass.full_exception.nil?
-
# Display a stack trace in the css output when in development-like environments.
-
config.sass.full_exception = app.config.consider_all_requests_local
-
end
-
app.assets.context_class.extend(SassContext)
-
app.assets.context_class.sass_config = app.config.sass
-
-
Sass.logger = app.config.sass.logger
-
end
-
-
1
initializer :setup_compression, :group => :all do |app|
-
if app.config.assets.compress
-
app.config.sass.style = :compressed
-
app.config.assets.css_compressor = CssCompressor.new
-
end
-
end
-
end
-
end
-
1
require 'tilt'
-
1
require 'sprockets'
-
-
1
module Sass::Rails
-
-
1
class Resolver
-
-
1
attr_accessor :context
-
-
1
def initialize(context)
-
@context = context
-
end
-
-
1
def resolve(path, content_type = :self)
-
options = {}
-
options[:content_type] = content_type unless content_type.nil?
-
context.resolve(path, options)
-
rescue Sprockets::FileNotFound, Sprockets::ContentTypeMismatch
-
nil
-
end
-
-
1
def public_path(path, scope = nil)
-
context.asset_paths.compute_public_path(path, ::Rails.application.config.assets.prefix)
-
end
-
-
1
def process(path)
-
context.environment[path].to_s
-
end
-
-
1
def image_path(img)
-
context.image_path(img)
-
end
-
-
1
def video_path(video)
-
context.video_path(video)
-
end
-
-
1
def audio_path(audio)
-
context.audio_path(audio)
-
end
-
-
1
def javascript_path(javascript)
-
context.javascript_path(javascript)
-
end
-
-
1
def stylesheet_path(stylesheet)
-
context.stylesheet_path(stylesheet)
-
end
-
end
-
-
1
class SassTemplate < Tilt::SassTemplate
-
1
self.default_mime_type = 'text/css'
-
-
1
def self.engine_initialized?
-
defined?(::Sass::Engine)
-
end
-
-
1
def initialize_engine
-
require_template_library 'sass'
-
end
-
-
1
def syntax
-
:sass
-
end
-
-
1
def sass_options_from_rails(scope)
-
scope.environment.context_class.sass_config
-
end
-
-
1
def sass_options(scope)
-
importer = self.importer(scope)
-
options = sass_options_from_rails(scope)
-
load_paths = (options[:load_paths] || []).dup
-
load_paths.unshift(importer)
-
resolver = Resolver.new(scope)
-
css_filename = File.join(::Rails.public_path, resolver.public_path(scope.logical_path)) + ".css"
-
options.merge(
-
:filename => eval_file,
-
:css_filename => css_filename,
-
:line => line,
-
:syntax => syntax,
-
:importer => importer,
-
:load_paths => load_paths,
-
:custom => {
-
:resolver => resolver
-
}
-
)
-
end
-
-
1
def importer(scope)
-
Sass::Rails::Importer.new(scope)
-
end
-
-
1
def prepare
-
end
-
-
1
def evaluate(scope, locals, &block)
-
Sass::Engine.new(data, sass_options(scope)).render
-
end
-
end
-
-
1
class ScssTemplate < SassTemplate
-
1
self.default_mime_type = 'text/css'
-
-
1
def syntax
-
:scss
-
end
-
end
-
end
-
1
module Sass
-
1
module Rails
-
1
VERSION = "3.1.6"
-
end
-
end
-
1
require 'sprockets/version'
-
-
1
module Sprockets
-
1
autoload :ArgumentError, "sprockets/errors"
-
1
autoload :Asset, "sprockets/asset"
-
1
autoload :AssetAttributes, "sprockets/asset_attributes"
-
1
autoload :BundledAsset, "sprockets/bundled_asset"
-
1
autoload :CharsetNormalizer, "sprockets/charset_normalizer"
-
1
autoload :CircularDependencyError, "sprockets/errors"
-
1
autoload :ContentTypeMismatch, "sprockets/errors"
-
1
autoload :Context, "sprockets/context"
-
1
autoload :DirectiveProcessor, "sprockets/directive_processor"
-
1
autoload :EcoTemplate, "sprockets/eco_template"
-
1
autoload :EjsTemplate, "sprockets/ejs_template"
-
1
autoload :EngineError, "sprockets/errors"
-
1
autoload :Engines, "sprockets/engines"
-
1
autoload :Environment, "sprockets/environment"
-
1
autoload :Error, "sprockets/errors"
-
1
autoload :FileNotFound, "sprockets/errors"
-
1
autoload :Index, "sprockets/index"
-
1
autoload :JstProcessor, "sprockets/jst_processor"
-
1
autoload :Processing, "sprockets/processing"
-
1
autoload :Processor, "sprockets/processor"
-
1
autoload :Server, "sprockets/server"
-
1
autoload :StaticAsset, "sprockets/static_asset"
-
1
autoload :Utils, "sprockets/utils"
-
-
1
module Cache
-
1
autoload :FileStore, "sprockets/cache/file_store"
-
end
-
end
-
1
module Sprockets
-
1
VERSION = "2.0.3"
-
end
-
1
module Tilt
-
1
VERSION = '1.3.3'
-
-
1
@preferred_mappings = Hash.new
-
25
@template_mappings = Hash.new { |h, k| h[k] = [] }
-
-
# Hash of template path pattern => template implementation class mappings.
-
1
def self.mappings
-
39
@template_mappings
-
end
-
-
1
def self.normalize(ext)
-
39
ext.to_s.downcase.sub(/^\./, '')
-
end
-
-
# Register a template implementation by file extension.
-
1
def self.register(template_class, *extensions)
-
23
if template_class.respond_to?(:to_str)
-
# Support register(ext, template_class) too
-
extensions, template_class = [template_class], extensions[0]
-
end
-
-
23
extensions.each do |ext|
-
39
ext = normalize(ext)
-
39
mappings[ext].unshift(template_class).uniq!
-
end
-
end
-
-
# Makes a template class preferred for the given file extensions. If you
-
# don't provide any extensions, it will be preferred for all its already
-
# registered extensions:
-
#
-
# # Prefer RDiscount for its registered file extensions:
-
# Tilt.prefer(Tilt::RDiscountTemplate)
-
#
-
# # Prefer RDiscount only for the .md extensions:
-
# Tilt.prefer(Tilt::RDiscountTemplate, '.md')
-
1
def self.prefer(template_class, *extensions)
-
if extensions.empty?
-
mappings.each do |ext, klasses|
-
@preferred_mappings[ext] = template_class if klasses.include? template_class
-
end
-
else
-
extensions.each do |ext|
-
ext = normalize(ext)
-
register(template_class, ext)
-
@preferred_mappings[ext] = template_class
-
end
-
end
-
end
-
-
# Returns true when a template exists on an exact match of the provided file extension
-
1
def self.registered?(ext)
-
mappings.key?(ext.downcase) && !mappings[ext.downcase].empty?
-
end
-
-
# Create a new template for the given file using the file's extension
-
# to determine the the template mapping.
-
1
def self.new(file, line=nil, options={}, &block)
-
if template_class = self[file]
-
template_class.new(file, line, options, &block)
-
else
-
fail "No template engine registered for #{File.basename(file)}"
-
end
-
end
-
-
# Lookup a template class for the given filename or file
-
# extension. Return nil when no implementation is found.
-
1
def self.[](file)
-
pattern = file.to_s.downcase
-
until pattern.empty? || registered?(pattern)
-
pattern = File.basename(pattern)
-
pattern.sub!(/^[^.]*\.?/, '')
-
end
-
-
# Try to find a preferred engine.
-
preferred_klass = @preferred_mappings[pattern]
-
return preferred_klass if preferred_klass
-
-
# Fall back to the general list of mappings.
-
klasses = @template_mappings[pattern]
-
-
# Try to find an engine which is already loaded.
-
template = klasses.detect do |klass|
-
if klass.respond_to?(:engine_initialized?)
-
klass.engine_initialized?
-
end
-
end
-
-
return template if template
-
-
# Try each of the classes until one succeeds. If all of them fails,
-
# we'll raise the error of the first class.
-
first_failure = nil
-
-
klasses.each do |klass|
-
begin
-
klass.new { '' }
-
rescue Exception => ex
-
first_failure ||= ex
-
next
-
else
-
return klass
-
end
-
end
-
-
raise first_failure if first_failure
-
end
-
-
# Deprecated module.
-
1
module CompileSite
-
end
-
-
# Extremely simple template cache implementation. Calling applications
-
# create a Tilt::Cache instance and use #fetch with any set of hashable
-
# arguments (such as those to Tilt.new):
-
# cache = Tilt::Cache.new
-
# cache.fetch(path, line, options) { Tilt.new(path, line, options) }
-
#
-
# Subsequent invocations return the already loaded template object.
-
1
class Cache
-
1
def initialize
-
@cache = {}
-
end
-
-
1
def fetch(*key)
-
@cache[key] ||= yield
-
end
-
-
1
def clear
-
@cache = {}
-
end
-
end
-
-
-
# Template Implementations ================================================
-
-
1
require 'tilt/string'
-
1
register StringTemplate, 'str'
-
-
1
require 'tilt/erb'
-
1
register ERBTemplate, 'erb', 'rhtml'
-
1
register ErubisTemplate, 'erb', 'rhtml', 'erubis'
-
-
1
require 'tilt/haml'
-
1
register HamlTemplate, 'haml'
-
-
1
require 'tilt/css'
-
1
register SassTemplate, 'sass'
-
1
register ScssTemplate, 'scss'
-
1
register LessTemplate, 'less'
-
-
1
require 'tilt/coffee'
-
1
register CoffeeScriptTemplate, 'coffee'
-
-
1
require 'tilt/nokogiri'
-
1
register NokogiriTemplate, 'nokogiri'
-
-
1
require 'tilt/builder'
-
1
register BuilderTemplate, 'builder'
-
-
1
require 'tilt/markaby'
-
1
register MarkabyTemplate, 'mab'
-
-
1
require 'tilt/liquid'
-
1
register LiquidTemplate, 'liquid'
-
-
1
require 'tilt/radius'
-
1
register RadiusTemplate, 'radius'
-
-
1
require 'tilt/markdown'
-
1
register MarukuTemplate, 'markdown', 'mkd', 'md'
-
1
register KramdownTemplate, 'markdown', 'mkd', 'md'
-
1
register BlueClothTemplate, 'markdown', 'mkd', 'md'
-
1
register RDiscountTemplate, 'markdown', 'mkd', 'md'
-
1
register RedcarpetTemplate, 'markdown', 'mkd', 'md'
-
-
1
require 'tilt/textile'
-
1
register RedClothTemplate, 'textile'
-
-
1
require 'tilt/rdoc'
-
1
register RDocTemplate, 'rdoc'
-
-
1
require 'tilt/wiki'
-
1
register CreoleTemplate, 'wiki', 'creole'
-
1
register WikiClothTemplate, 'wiki', 'mediawiki', 'mw'
-
-
1
require 'tilt/yajl'
-
1
register YajlTemplate, 'yajl'
-
end
-
1
require 'tilt/template'
-
-
1
module Tilt
-
# Builder template implementation. See:
-
# http://builder.rubyforge.org/
-
1
class BuilderTemplate < Template
-
1
self.default_mime_type = 'text/xml'
-
-
1
def self.engine_initialized?
-
defined? ::Builder
-
end
-
-
1
def initialize_engine
-
require_template_library 'builder'
-
end
-
-
1
def prepare; end
-
-
1
def evaluate(scope, locals, &block)
-
return super(scope, locals, &block) if data.respond_to?(:to_str)
-
xml = ::Builder::XmlMarkup.new(:indent => 2)
-
data.call(xml)
-
xml.target!
-
end
-
-
1
def precompiled_preamble(locals)
-
return super if locals.include? :xml
-
"xml = ::Builder::XmlMarkup.new(:indent => 2)\n#{super}"
-
end
-
-
1
def precompiled_postamble(locals)
-
"xml.target!"
-
end
-
-
1
def precompiled_template(locals)
-
data.to_str
-
end
-
end
-
end
-
-
1
require 'tilt/template'
-
-
1
module Tilt
-
# CoffeeScript template implementation. See:
-
# http://coffeescript.org/
-
#
-
# CoffeeScript templates do not support object scopes, locals, or yield.
-
1
class CoffeeScriptTemplate < Template
-
1
self.default_mime_type = 'application/javascript'
-
-
1
@@default_bare = false
-
-
1
def self.default_bare
-
@@default_bare
-
end
-
-
1
def self.default_bare=(value)
-
@@default_bare = value
-
end
-
-
# DEPRECATED
-
1
def self.default_no_wrap
-
@@default_bare
-
end
-
-
# DEPRECATED
-
1
def self.default_no_wrap=(value)
-
@@default_bare = value
-
end
-
-
1
def self.engine_initialized?
-
defined? ::CoffeeScript
-
end
-
-
1
def initialize_engine
-
require_template_library 'coffee_script'
-
end
-
-
1
def prepare
-
if !options.key?(:bare) and !options.key?(:no_wrap)
-
options[:bare] = self.class.default_bare
-
end
-
end
-
-
1
def evaluate(scope, locals, &block)
-
@output ||= CoffeeScript.compile(data, options)
-
end
-
end
-
end
-
-
1
require 'tilt/template'
-
-
1
module Tilt
-
# Sass template implementation. See:
-
# http://haml.hamptoncatlin.com/
-
#
-
# Sass templates do not support object scopes, locals, or yield.
-
1
class SassTemplate < Template
-
1
self.default_mime_type = 'text/css'
-
-
1
def self.engine_initialized?
-
defined? ::Sass::Engine
-
end
-
-
1
def initialize_engine
-
require_template_library 'sass'
-
end
-
-
1
def prepare
-
@engine = ::Sass::Engine.new(data, sass_options)
-
end
-
-
1
def evaluate(scope, locals, &block)
-
@output ||= @engine.render
-
end
-
-
1
private
-
1
def sass_options
-
options.merge(:filename => eval_file, :line => line, :syntax => :sass)
-
end
-
end
-
-
# Sass's new .scss type template implementation.
-
1
class ScssTemplate < SassTemplate
-
1
self.default_mime_type = 'text/css'
-
-
1
private
-
1
def sass_options
-
options.merge(:filename => eval_file, :line => line, :syntax => :scss)
-
end
-
end
-
-
# Lessscss template implementation. See:
-
# http://lesscss.org/
-
#
-
# Less templates do not support object scopes, locals, or yield.
-
1
class LessTemplate < Template
-
1
self.default_mime_type = 'text/css'
-
-
1
def self.engine_initialized?
-
defined? ::Less
-
end
-
-
1
def initialize_engine
-
require_template_library 'less'
-
end
-
-
1
def prepare
-
if ::Less.const_defined? :Engine
-
@engine = ::Less::Engine.new(data)
-
else
-
parser = ::Less::Parser.new(:filename => eval_file, :line => line)
-
@engine = parser.parse(data)
-
end
-
end
-
-
1
def evaluate(scope, locals, &block)
-
@output ||= @engine.to_css
-
end
-
end
-
end
-
-
1
require 'tilt/template'
-
-
1
module Tilt
-
# ERB template implementation. See:
-
# http://www.ruby-doc.org/stdlib/libdoc/erb/rdoc/classes/ERB.html
-
1
class ERBTemplate < Template
-
1
@@default_output_variable = '_erbout'
-
-
1
def self.default_output_variable
-
@@default_output_variable
-
end
-
-
1
def self.default_output_variable=(name)
-
@@default_output_variable = name
-
end
-
-
1
def self.engine_initialized?
-
defined? ::ERB
-
end
-
-
1
def initialize_engine
-
require_template_library 'erb'
-
end
-
-
1
def prepare
-
@outvar = options[:outvar] || self.class.default_output_variable
-
options[:trim] = '<>' if options[:trim].nil? || options[:trim] == true
-
@engine = ::ERB.new(data, options[:safe], options[:trim], @outvar)
-
end
-
-
1
def precompiled_template(locals)
-
source = @engine.src
-
source
-
end
-
-
1
def precompiled_preamble(locals)
-
<<-RUBY
-
begin
-
__original_outvar = #{@outvar} if defined?(#{@outvar})
-
#{super}
-
RUBY
-
end
-
-
1
def precompiled_postamble(locals)
-
<<-RUBY
-
#{super}
-
ensure
-
#{@outvar} = __original_outvar
-
end
-
RUBY
-
end
-
-
# ERB generates a line to specify the character coding of the generated
-
# source in 1.9. Account for this in the line offset.
-
1
if RUBY_VERSION >= '1.9.0'
-
1
def precompiled(locals)
-
source, offset = super
-
[source, offset + 1]
-
end
-
end
-
end
-
-
# Erubis template implementation. See:
-
# http://www.kuwata-lab.com/erubis/
-
#
-
# ErubisTemplate supports the following additional options, which are not
-
# passed down to the Erubis engine:
-
#
-
# :engine_class allows you to specify a custom engine class to use
-
# instead of the default (which is ::Erubis::Eruby).
-
#
-
# :escape_html when true, ::Erubis::EscapedEruby will be used as
-
# the engine class instead of the default. All content
-
# within <%= %> blocks will be automatically html escaped.
-
1
class ErubisTemplate < ERBTemplate
-
1
def self.engine_initialized?
-
defined? ::Erubis
-
end
-
-
1
def initialize_engine
-
require_template_library 'erubis'
-
end
-
-
1
def prepare
-
@outvar = options.delete(:outvar) || self.class.default_output_variable
-
@options.merge!(:preamble => false, :postamble => false, :bufvar => @outvar)
-
engine_class = options.delete(:engine_class)
-
engine_class = ::Erubis::EscapedEruby if options.delete(:escape_html)
-
@engine = (engine_class || ::Erubis::Eruby).new(data, options)
-
end
-
-
1
def precompiled_preamble(locals)
-
[super, "#{@outvar} = _buf = ''"].join("\n")
-
end
-
-
1
def precompiled_postamble(locals)
-
[@outvar, super].join("\n")
-
end
-
-
# Erubis doesn't have ERB's line-off-by-one under 1.9 problem.
-
# Override and adjust back.
-
1
if RUBY_VERSION >= '1.9.0'
-
1
def precompiled(locals)
-
source, offset = super
-
[source, offset - 1]
-
end
-
end
-
end
-
end
-
-
1
require 'tilt/template'
-
-
1
module Tilt
-
# Haml template implementation. See:
-
# http://haml.hamptoncatlin.com/
-
1
class HamlTemplate < Template
-
1
self.default_mime_type = 'text/html'
-
-
1
def self.engine_initialized?
-
defined? ::Haml::Engine
-
end
-
-
1
def initialize_engine
-
require_template_library 'haml'
-
end
-
-
1
def prepare
-
options = @options.merge(:filename => eval_file, :line => line)
-
@engine = ::Haml::Engine.new(data, options)
-
end
-
-
1
def evaluate(scope, locals, &block)
-
if @engine.respond_to?(:precompiled_method_return_value, true)
-
super
-
else
-
@engine.render(scope, locals, &block)
-
end
-
end
-
-
# Precompiled Haml source. Taken from the precompiled_with_ambles
-
# method in Haml::Precompiler:
-
# http://github.com/nex3/haml/blob/master/lib/haml/precompiler.rb#L111-126
-
1
def precompiled_template(locals)
-
@engine.precompiled
-
end
-
-
1
def precompiled_preamble(locals)
-
local_assigns = super
-
@engine.instance_eval do
-
<<-RUBY
-
begin
-
extend Haml::Helpers
-
_hamlout = @haml_buffer = Haml::Buffer.new(@haml_buffer, #{options_for_buffer.inspect})
-
_erbout = _hamlout.buffer
-
__in_erb_template = true
-
_haml_locals = locals
-
#{local_assigns}
-
RUBY
-
end
-
end
-
-
1
def precompiled_postamble(locals)
-
@engine.instance_eval do
-
<<-RUBY
-
#{precompiled_method_return_value}
-
ensure
-
@haml_buffer = @haml_buffer.upper
-
end
-
RUBY
-
end
-
end
-
end
-
end
-
-
1
require 'tilt/template'
-
-
1
module Tilt
-
# Liquid template implementation. See:
-
# http://liquid.rubyforge.org/
-
#
-
# Liquid is designed to be a *safe* template system and threfore
-
# does not provide direct access to execuatable scopes. In order to
-
# support a +scope+, the +scope+ must be able to represent itself
-
# as a hash by responding to #to_h. If the +scope+ does not respond
-
# to #to_h it will be ignored.
-
#
-
# LiquidTemplate does not support yield blocks.
-
#
-
# It's suggested that your program require 'liquid' at load
-
# time when using this template engine.
-
1
class LiquidTemplate < Template
-
1
def self.engine_initialized?
-
defined? ::Liquid::Template
-
end
-
-
1
def initialize_engine
-
require_template_library 'liquid'
-
end
-
-
1
def prepare
-
@engine = ::Liquid::Template.parse(data)
-
end
-
-
1
def evaluate(scope, locals, &block)
-
locals = locals.inject({}){ |h,(k,v)| h[k.to_s] = v ; h }
-
if scope.respond_to?(:to_h)
-
scope = scope.to_h.inject({}){ |h,(k,v)| h[k.to_s] = v ; h }
-
locals = scope.merge(locals)
-
end
-
locals['yield'] = block.nil? ? '' : yield
-
locals['content'] = locals['yield']
-
@engine.render(locals)
-
end
-
end
-
end
-
1
require 'tilt/template'
-
-
1
module Tilt
-
# Markaby
-
# http://github.com/markaby/markaby
-
1
class MarkabyTemplate < Template
-
1
def self.builder_class
-
@builder_class ||= Class.new(Markaby::Builder) do
-
def __capture_markaby_tilt__(&block)
-
__run_markaby_tilt__ do
-
text capture(&block)
-
end
-
end
-
end
-
end
-
-
1
def self.engine_initialized?
-
defined? ::Markaby
-
end
-
-
1
def initialize_engine
-
require_template_library 'markaby'
-
end
-
-
1
def prepare
-
end
-
-
1
def evaluate(scope, locals, &block)
-
builder = self.class.builder_class.new({}, scope)
-
builder.locals = locals
-
-
if data.kind_of? Proc
-
(class << builder; self end).send(:define_method, :__run_markaby_tilt__, &data)
-
else
-
builder.instance_eval <<-CODE, __FILE__, __LINE__
-
def __run_markaby_tilt__
-
#{data}
-
end
-
CODE
-
end
-
-
if block
-
builder.__capture_markaby_tilt__(&block)
-
else
-
builder.__run_markaby_tilt__
-
end
-
-
builder.to_s
-
end
-
end
-
end
-
-
1
require 'tilt/template'
-
-
1
module Tilt
-
# Discount Markdown implementation. See:
-
# http://github.com/rtomayko/rdiscount
-
#
-
# RDiscount is a simple text filter. It does not support +scope+ or
-
# +locals+. The +:smart+ and +:filter_html+ options may be set true
-
# to enable those flags on the underlying RDiscount object.
-
1
class RDiscountTemplate < Template
-
1
self.default_mime_type = 'text/html'
-
-
1
ALIAS = {
-
:escape_html => :filter_html,
-
:smartypants => :smart
-
}
-
-
1
FLAGS = [:smart, :filter_html, :smartypants, :escape_html]
-
-
1
def flags
-
FLAGS.select { |flag| options[flag] }.map { |flag| ALIAS[flag] || flag }
-
end
-
-
1
def self.engine_initialized?
-
defined? ::RDiscount
-
end
-
-
1
def initialize_engine
-
require_template_library 'rdiscount'
-
end
-
-
1
def prepare
-
@engine = RDiscount.new(data, *flags)
-
@output = nil
-
end
-
-
1
def evaluate(scope, locals, &block)
-
@output ||= @engine.to_html
-
end
-
end
-
-
# Upskirt Markdown implementation. See:
-
# https://github.com/tanoku/redcarpet
-
#
-
# Supports both Redcarpet 1.x and 2.x
-
1
class RedcarpetTemplate < Template
-
1
def self.engine_initialized?
-
defined? ::Redcarpet
-
end
-
-
1
def initialize_engine
-
require_template_library 'redcarpet'
-
end
-
-
1
def prepare
-
klass = [Redcarpet1, Redcarpet2].detect { |e| e.engine_initialized? }
-
@engine = klass.new(file, line, options) { data }
-
end
-
-
1
def evaluate(scope, locals, &block)
-
@engine.evaluate(scope, locals, &block)
-
end
-
-
# Compatibility mode for Redcarpet 1.x
-
1
class Redcarpet1 < RDiscountTemplate
-
1
self.default_mime_type = 'text/html'
-
-
1
def self.engine_initialized?
-
defined? ::RedcarpetCompat
-
end
-
-
1
def prepare
-
@engine = RedcarpetCompat.new(data, *flags)
-
@output = nil
-
end
-
end
-
-
# Future proof mode for Redcarpet 2.x (not yet released)
-
1
class Redcarpet2 < Template
-
1
self.default_mime_type = 'text/html'
-
-
1
def self.engine_initialized?
-
defined? ::Redcarpet::Render
-
end
-
-
1
def generate_renderer
-
renderer = options.delete(:renderer) || Redcarpet::Render::HTML
-
return renderer unless options.delete(:smartypants)
-
return renderer if renderer <= Redcarpet::Render::SmartyPants
-
-
if renderer == Redcarpet::Render::XHTML
-
Redcarpet::Render::SmartyHTML.new(:xhtml => true)
-
elsif renderer == Redcarpet::Render::HTML
-
Redcarpet::Render::SmartyHTML
-
elsif renderer.is_a? Class
-
Class.new(renderer) { include Redcarpet::Render::SmartyPants }
-
else
-
renderer.extend Redcarpet::Render::SmartyPants
-
end
-
end
-
-
1
def prepare
-
# try to support the same aliases
-
RDiscountTemplate::ALIAS.each do |opt, aka|
-
next if options.key? opt or not options.key? aka
-
options[opt] = options.delete(aka)
-
end
-
-
# only raise an exception if someone is trying to enable :escape_html
-
options.delete(:escape_html) unless options[:escape_html]
-
-
@engine = Redcarpet::Markdown.new(generate_renderer, options)
-
@output = nil
-
end
-
-
1
def evaluate(scope, locals, &block)
-
@output ||= @engine.render(data)
-
end
-
end
-
end
-
-
# BlueCloth Markdown implementation. See:
-
# http://deveiate.org/projects/BlueCloth/
-
1
class BlueClothTemplate < Template
-
1
self.default_mime_type = 'text/html'
-
-
1
def self.engine_initialized?
-
defined? ::BlueCloth
-
end
-
-
1
def initialize_engine
-
require_template_library 'bluecloth'
-
end
-
-
1
def prepare
-
@engine = BlueCloth.new(data, options)
-
@output = nil
-
end
-
-
1
def evaluate(scope, locals, &block)
-
@output ||= @engine.to_html
-
end
-
end
-
-
# Maruku markdown implementation. See:
-
# http://maruku.rubyforge.org/
-
1
class MarukuTemplate < Template
-
1
def self.engine_initialized?
-
defined? ::Maruku
-
end
-
-
1
def initialize_engine
-
require_template_library 'maruku'
-
end
-
-
1
def prepare
-
@engine = Maruku.new(data, options)
-
@output = nil
-
end
-
-
1
def evaluate(scope, locals, &block)
-
@output ||= @engine.to_html
-
end
-
end
-
-
# Kramdown Markdown implementation. See:
-
# http://kramdown.rubyforge.org/
-
1
class KramdownTemplate < Template
-
1
DUMB_QUOTES = [39, 39, 34, 34]
-
-
1
def self.engine_initialized?
-
defined? ::Kramdown
-
end
-
-
1
def initialize_engine
-
require_template_library 'kramdown'
-
end
-
-
1
def prepare
-
options[:smart_quotes] = DUMB_QUOTES unless options[:smartypants]
-
@engine = Kramdown::Document.new(data, options)
-
@output = nil
-
end
-
-
1
def evaluate(scope, locals, &block)
-
@output ||= @engine.to_html
-
end
-
end
-
end
-
-
1
require 'tilt/template'
-
-
1
module Tilt
-
# Nokogiri template implementation. See:
-
# http://nokogiri.org/
-
1
class NokogiriTemplate < Template
-
1
self.default_mime_type = 'text/xml'
-
-
1
def self.engine_initialized?
-
defined? ::Nokogiri
-
end
-
-
1
def initialize_engine
-
require_template_library 'nokogiri'
-
end
-
-
1
def prepare; end
-
-
1
def evaluate(scope, locals, &block)
-
block &&= proc { yield.gsub(/^<\?xml version=\"1\.0\"\?>\n?/, "") }
-
-
if data.respond_to?(:to_str)
-
super(scope, locals, &block)
-
else
-
::Nokogiri::XML::Builder.new.tap(&data).to_xml
-
end
-
end
-
-
1
def precompiled_preamble(locals)
-
return super if locals.include? :xml
-
"xml = ::Nokogiri::XML::Builder.new { |xml| }\n#{super}"
-
end
-
-
1
def precompiled_postamble(locals)
-
"xml.to_xml"
-
end
-
-
1
def precompiled_template(locals)
-
data.to_str
-
end
-
end
-
end
-
-
1
require 'tilt/template'
-
-
1
module Tilt
-
# Radius Template
-
# http://github.com/jlong/radius/
-
1
class RadiusTemplate < Template
-
1
def self.engine_initialized?
-
defined? ::Radius
-
end
-
-
1
def self.context_class
-
@context_class ||= Class.new(Radius::Context) do
-
attr_accessor :tilt_scope
-
-
def tag_missing(name, attributes)
-
tilt_scope.__send__(name)
-
end
-
-
def dup
-
i = super
-
i.tilt_scope = tilt_scope
-
i
-
end
-
end
-
end
-
-
1
def initialize_engine
-
require_template_library 'radius'
-
end
-
-
1
def prepare
-
end
-
-
1
def evaluate(scope, locals, &block)
-
context = self.class.context_class.new
-
context.tilt_scope = scope
-
context.define_tag("yield") do
-
block.call
-
end
-
locals.each do |tag, value|
-
context.define_tag(tag) do
-
value
-
end
-
end
-
-
options = {:tag_prefix => 'r'}.merge(@options)
-
parser = Radius::Parser.new(context, options)
-
parser.parse(data)
-
end
-
end
-
end
-
1
require 'tilt/template'
-
-
1
module Tilt
-
# RDoc template. See:
-
# http://rdoc.rubyforge.org/
-
#
-
# It's suggested that your program require 'rdoc/markup' and
-
# 'rdoc/markup/to_html' at load time when using this template
-
# engine.
-
1
class RDocTemplate < Template
-
1
self.default_mime_type = 'text/html'
-
-
1
def self.engine_initialized?
-
defined? ::RDoc::Markup
-
end
-
-
1
def initialize_engine
-
require_template_library 'rdoc/markup'
-
require_template_library 'rdoc/markup/to_html'
-
end
-
-
1
def prepare
-
markup = RDoc::Markup::ToHtml.new
-
@engine = markup.convert(data)
-
@output = nil
-
end
-
-
1
def evaluate(scope, locals, &block)
-
@output ||= @engine.to_s
-
end
-
end
-
end
-
1
require 'tilt/template'
-
-
1
module Tilt
-
# The template source is evaluated as a Ruby string. The #{} interpolation
-
# syntax can be used to generated dynamic output.
-
1
class StringTemplate < Template
-
1
def prepare
-
hash = "TILT#{data.hash.abs}"
-
@code = "<<#{hash}.chomp\n#{data}\n#{hash}"
-
end
-
-
1
def precompiled_template(locals)
-
@code
-
end
-
-
1
def precompiled(locals)
-
source, offset = super
-
[source, offset + 1]
-
end
-
end
-
end
-
1
module Tilt
-
1
TOPOBJECT = defined?(BasicObject) ? BasicObject : Object
-
-
# Base class for template implementations. Subclasses must implement
-
# the #prepare method and one of the #evaluate or #precompiled_template
-
# methods.
-
1
class Template
-
# Template source; loaded from a file or given directly.
-
1
attr_reader :data
-
-
# The name of the file where the template data was loaded from.
-
1
attr_reader :file
-
-
# The line number in #file where template data was loaded from.
-
1
attr_reader :line
-
-
# A Hash of template engine specific options. This is passed directly
-
# to the underlying engine and is not used by the generic template
-
# interface.
-
1
attr_reader :options
-
-
# Used to determine if this class's initialize_engine method has
-
# been called yet.
-
1
@engine_initialized = false
-
1
class << self
-
1
attr_accessor :engine_initialized
-
1
alias engine_initialized? engine_initialized
-
-
1
attr_accessor :default_mime_type
-
end
-
-
# Create a new template with the file, line, and options specified. By
-
# default, template data is read from the file. When a block is given,
-
# it should read template data and return as a String. When file is nil,
-
# a block is required.
-
#
-
# All arguments are optional.
-
1
def initialize(file=nil, line=1, options={}, &block)
-
@file, @line, @options = nil, 1, {}
-
-
[options, line, file].compact.each do |arg|
-
case
-
when arg.respond_to?(:to_str) ; @file = arg.to_str
-
when arg.respond_to?(:to_int) ; @line = arg.to_int
-
when arg.respond_to?(:to_hash) ; @options = arg.to_hash.dup
-
else raise TypeError
-
end
-
end
-
-
raise ArgumentError, "file or block required" if (@file || block).nil?
-
-
# call the initialize_engine method if this is the very first time
-
# an instance of this class has been created.
-
if !self.class.engine_initialized?
-
initialize_engine
-
self.class.engine_initialized = true
-
end
-
-
# used to hold compiled template methods
-
@compiled_method = {}
-
-
# used on 1.9 to set the encoding if it is not set elsewhere (like a magic comment)
-
# currently only used if template compiles to ruby
-
@default_encoding = @options.delete :default_encoding
-
-
# load template data and prepare (uses binread to avoid encoding issues)
-
@reader = block || lambda { |t| File.respond_to?(:binread) ? File.binread(@file) : File.read(@file) }
-
@data = @reader.call(self)
-
prepare
-
end
-
-
# Render the template in the given scope with the locals specified. If a
-
# block is given, it is typically available within the template via
-
# +yield+.
-
1
def render(scope=Object.new, locals={}, &block)
-
evaluate scope, locals || {}, &block
-
end
-
-
# The basename of the template file.
-
1
def basename(suffix='')
-
File.basename(file, suffix) if file
-
end
-
-
# The template file's basename with all extensions chomped off.
-
1
def name
-
basename.split('.', 2).first if basename
-
end
-
-
# The filename used in backtraces to describe the template.
-
1
def eval_file
-
file || '(__TEMPLATE__)'
-
end
-
-
1
protected
-
# Called once and only once for each template subclass the first time
-
# the template class is initialized. This should be used to require the
-
# underlying template library and perform any initial setup.
-
1
def initialize_engine
-
end
-
-
# Like Kernel::require but issues a warning urging a manual require when
-
# running under a threaded environment.
-
1
def require_template_library(name)
-
if Thread.list.size > 1
-
warn "WARN: tilt autoloading '#{name}' in a non thread-safe way; " +
-
"explicit require '#{name}' suggested."
-
end
-
require name
-
end
-
-
# Do whatever preparation is necessary to setup the underlying template
-
# engine. Called immediately after template data is loaded. Instance
-
# variables set in this method are available when #evaluate is called.
-
#
-
# Subclasses must provide an implementation of this method.
-
1
def prepare
-
if respond_to?(:compile!)
-
# backward compat with tilt < 0.6; just in case
-
warn 'Tilt::Template#compile! is deprecated; implement #prepare instead.'
-
compile!
-
else
-
raise NotImplementedError
-
end
-
end
-
-
1
def evaluate(scope, locals, &block)
-
cached_evaluate(scope, locals, &block)
-
end
-
-
# Process the template and return the result. The first time this
-
# method is called, the template source is evaluated with instance_eval.
-
# On the sequential method calls it will compile the template to an
-
# unbound method which will lead to better performance. In any case,
-
# template executation is guaranteed to be performed in the scope object
-
# with the locals specified and with support for yielding to the block.
-
1
def cached_evaluate(scope, locals, &block)
-
# Redefine itself to use method compilation the next time:
-
def self.cached_evaluate(scope, locals, &block)
-
method = compiled_method(locals.keys)
-
method.bind(scope).call(locals, &block)
-
end
-
-
# Use instance_eval the first time:
-
evaluate_source(scope, locals, &block)
-
end
-
-
# Generates all template source by combining the preamble, template, and
-
# postamble and returns a two-tuple of the form: [source, offset], where
-
# source is the string containing (Ruby) source code for the template and
-
# offset is the integer line offset where line reporting should begin.
-
#
-
# Template subclasses may override this method when they need complete
-
# control over source generation or want to adjust the default line
-
# offset. In most cases, overriding the #precompiled_template method is
-
# easier and more appropriate.
-
1
def precompiled(locals)
-
preamble = precompiled_preamble(locals)
-
template = precompiled_template(locals)
-
magic_comment = extract_magic_comment(template)
-
if magic_comment
-
# Magic comment e.g. "# coding: utf-8" has to be in the first line.
-
# So we copy the magic comment to the first line.
-
preamble = magic_comment + "\n" + preamble
-
end
-
parts = [
-
preamble,
-
template,
-
precompiled_postamble(locals)
-
]
-
[parts.join("\n"), preamble.count("\n") + 1]
-
end
-
-
# A string containing the (Ruby) source code for the template. The
-
# default Template#evaluate implementation requires either this method
-
# or the #precompiled method be overridden. When defined, the base
-
# Template guarantees correct file/line handling, locals support, custom
-
# scopes, and support for template compilation when the scope object
-
# allows it.
-
1
def precompiled_template(locals)
-
raise NotImplementedError
-
end
-
-
# Generates preamble code for initializing template state, and performing
-
# locals assignment. The default implementation performs locals
-
# assignment only. Lines included in the preamble are subtracted from the
-
# source line offset, so adding code to the preamble does not effect line
-
# reporting in Kernel::caller and backtraces.
-
1
def precompiled_preamble(locals)
-
locals.map { |k,v| "#{k} = locals[#{k.inspect}]" }.join("\n")
-
end
-
-
# Generates postamble code for the precompiled template source. The
-
# string returned from this method is appended to the precompiled
-
# template source.
-
1
def precompiled_postamble(locals)
-
''
-
end
-
-
# The compiled method for the locals keys provided.
-
1
def compiled_method(locals_keys)
-
@compiled_method[locals_keys] ||=
-
compile_template_method(locals_keys)
-
end
-
-
1
private
-
# Evaluate the template source in the context of the scope object.
-
1
def evaluate_source(scope, locals, &block)
-
source, offset = precompiled(locals)
-
scope.instance_eval(source, eval_file, line - offset)
-
end
-
-
# JRuby doesn't allow Object#instance_eval to yield to the block it's
-
# closed over. This is by design and (ostensibly) something that will
-
# change in MRI, though no current MRI version tested (1.8.6 - 1.9.2)
-
# exhibits the behavior. More info here:
-
#
-
# http://jira.codehaus.org/browse/JRUBY-2599
-
#
-
# We redefine evaluate_source to work around this issues.
-
1
if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby'
-
undef evaluate_source
-
def evaluate_source(scope, locals, &block)
-
source, offset = precompiled(locals)
-
file, lineno = eval_file, (line - offset)
-
scope.instance_eval { Kernel::eval(source, binding, file, lineno) }
-
end
-
end
-
-
1
def compile_template_method(locals)
-
source, offset = precompiled(locals)
-
offset += 5
-
method_name = "__tilt_#{Thread.current.object_id.abs}"
-
Object.class_eval <<-RUBY, eval_file, line - offset
-
#{extract_magic_comment source}
-
TOPOBJECT.class_eval do
-
def #{method_name}(locals)
-
Thread.current[:tilt_vars] = [self, locals]
-
class << self
-
this, locals = Thread.current[:tilt_vars]
-
this.instance_eval do
-
#{source}
-
end
-
end
-
end
-
end
-
RUBY
-
unbind_compiled_method(method_name)
-
end
-
-
1
def unbind_compiled_method(method_name)
-
method = TOPOBJECT.instance_method(method_name)
-
TOPOBJECT.class_eval { remove_method(method_name) }
-
method
-
end
-
-
1
def extract_magic_comment(script)
-
comment = script.slice(/\A[ \t]*\#.*coding\s*[=:]\s*([[:alnum:]\-_]+).*$/)
-
return comment if comment and not %w[ascii-8bit binary].include?($1.downcase)
-
"# coding: #{@default_encoding}" if @default_encoding
-
end
-
-
# Special case Ruby 1.9.1's broken yield.
-
#
-
# http://github.com/rtomayko/tilt/commit/20c01a5
-
# http://redmine.ruby-lang.org/issues/show/3601
-
#
-
# Remove when 1.9.2 dominates 1.9.1 installs in the wild.
-
1
if RUBY_VERSION =~ /^1.9.1/
-
undef compile_template_method
-
def compile_template_method(locals)
-
source, offset = precompiled(locals)
-
offset += 1
-
method_name = "__tilt_#{Thread.current.object_id}"
-
Object.class_eval <<-RUBY, eval_file, line - offset
-
TOPOBJECT.class_eval do
-
def #{method_name}(locals)
-
#{source}
-
end
-
end
-
RUBY
-
unbind_compiled_method(method_name)
-
end
-
end
-
end
-
end
-
1
require 'tilt/template'
-
-
1
module Tilt
-
# RedCloth implementation. See:
-
# http://redcloth.org/
-
1
class RedClothTemplate < Template
-
1
def self.engine_initialized?
-
defined? ::RedCloth
-
end
-
-
1
def initialize_engine
-
require_template_library 'redcloth'
-
end
-
-
1
def prepare
-
@engine = RedCloth.new(data)
-
@output = nil
-
end
-
-
1
def evaluate(scope, locals, &block)
-
@output ||= @engine.to_html
-
end
-
end
-
end
-
-
1
require 'tilt/template'
-
-
1
module Tilt
-
# Creole implementation. See:
-
# http://www.wikicreole.org/
-
1
class CreoleTemplate < Template
-
1
def self.engine_initialized?
-
defined? ::Creole
-
end
-
-
1
def initialize_engine
-
require_template_library 'creole'
-
end
-
-
1
def prepare
-
opts = {}
-
[:allowed_schemes, :extensions, :no_escape].each do |k|
-
opts[k] = options[k] if options[k]
-
end
-
@engine = Creole::Parser.new(data, opts)
-
@output = nil
-
end
-
-
1
def evaluate(scope, locals, &block)
-
@output ||= @engine.to_html
-
end
-
end
-
-
# WikiCloth implementation. See:
-
# http://redcloth.org/
-
1
class WikiClothTemplate < Template
-
1
def self.engine_initialized?
-
defined? ::WikiCloth::Parser
-
end
-
-
1
def initialize_engine
-
require_template_library 'wikicloth'
-
end
-
-
1
def prepare
-
@parser = options.delete(:parser) || WikiCloth::Parser
-
@engine = @parser.new options.merge(:data => data)
-
@output = nil
-
end
-
-
1
def evaluate(scope, locals, &block)
-
@output ||= @engine.to_html
-
end
-
end
-
end
-
1
require 'tilt/template'
-
-
1
module Tilt
-
-
# Yajl Template implementation
-
#
-
# Yajl is a fast JSON parsing and encoding library for Ruby
-
# See https://github.com/brianmario/yajl-ruby
-
#
-
# The template source is evaluated as a Ruby string,
-
# and the result is converted #to_json.
-
#
-
# == Example
-
#
-
# # This is a template example.
-
# # The template can contain any Ruby statement.
-
# tpl <<-EOS
-
# @counter = 0
-
#
-
# # The json variable represents the buffer
-
# # and holds the data to be serialized into json.
-
# # It defaults to an empty hash, but you can override it at any time.
-
# json = {
-
# :"user#{@counter += 1}" => { :name => "Joshua Peek", :id => @counter },
-
# :"user#{@counter += 1}" => { :name => "Ryan Tomayko", :id => @counter },
-
# :"user#{@counter += 1}" => { :name => "Simone Carletti", :id => @counter },
-
# }
-
#
-
# # Since the json variable is a Hash,
-
# # you can use conditional statements or any other Ruby statement
-
# # to populate it.
-
# json[:"user#{@counter += 1}"] = { :name => "Unknown" } if 1 == 2
-
#
-
# # The last line doesn't affect the returned value.
-
# nil
-
# EOS
-
#
-
# template = Tilt::YajlTemplate.new { tpl }
-
# template.render(self)
-
#
-
1
class YajlTemplate < Template
-
-
1
self.default_mime_type = 'application/json'
-
-
1
def self.engine_initialized?
-
defined? ::Yajl
-
end
-
-
1
def initialize_engine
-
require_template_library 'yajl'
-
end
-
-
1
def prepare
-
end
-
-
1
def evaluate(scope, locals, &block)
-
decorate super(scope, locals, &block)
-
end
-
-
1
def precompiled_preamble(locals)
-
return super if locals.include? :json
-
"json = {}\n#{super}"
-
end
-
-
1
def precompiled_postamble(locals)
-
"Yajl::Encoder.new.encode(json)"
-
end
-
-
1
def precompiled_template(locals)
-
data.to_str
-
end
-
-
-
# Decorates the +json+ input according to given +options+.
-
#
-
# json - The json String to decorate.
-
# options - The option Hash to customize the behavior.
-
#
-
# Returns the decorated String.
-
1
def decorate(json)
-
callback, variable = options[:callback], options[:variable]
-
if callback && variable
-
"var #{variable} = #{json}; #{callback}(#{variable});"
-
elsif variable
-
"var #{variable} = #{json};"
-
elsif callback
-
"#{callback}(#{json});"
-
else
-
json
-
end
-
end
-
end
-
-
end